# Building Cross-Language .NET and Python Agent Applications with AutoGen

> Build cross-language .NET and Python agent apps with AutoGen. Leverage gRPC and protobuf for seamless communication between your agents for powerful AI solutions.

- Repository: [Microsoft/autogen](https://github.com/microsoft/autogen)
- Tags: how-to-guide
- Published: 2026-03-07

---

**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`](https://github.com/microsoft/autogen/blob/main/autogen_core/_agent.py)<br>.NET: [`InProcessRuntime.cs`](https://github.com/microsoft/autogen/blob/main/InProcessRuntime.cs) |
| **AgentChat API** | Opinionated, high-level helper classes (e.g., `AssistantAgent`, `ToolAgent`) for rapid prototyping | Python: [`autogen_agentchat/agents.py`](https://github.com/microsoft/autogen/blob/main/autogen_agentchat/agents.py)<br>.NET: [`BaseAgent.cs`](https://github.com/microsoft/autogen/blob/main/BaseAgent.cs) |
| **Extensions** | Concrete implementations of LLM clients, tools, and runtime transports (gRPC, HTTP) | Python: [`autogen_ext/runtimes/grpc.py`](https://github.com/microsoft/autogen/blob/main/autogen_ext/runtimes/grpc.py)<br>.NET: [`GrpcWorkerAgentRuntime.cs`](https://github.com/microsoft/autogen/blob/main/GrpcWorkerAgentRuntime.cs) |
| **Samples** | Ready-to-run demos showing cross-language interoperability | Python: [`hello_python_agent.py`](https://github.com/microsoft/autogen/blob/main/hello_python_agent.py)<br>.NET: [`HelloAgent.cs`](https://github.com/microsoft/autogen/blob/main/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:

1. **Runtime Transport** – A `GrpcWorkerAgentRuntime` runs as a sidecar process in both Python ([`autogen_ext/runtimes/grpc.py`](https://github.com/microsoft/autogen/blob/main/autogen_ext/runtimes/grpc.py)) and .NET ([`GrpcWorkerAgentRuntime.cs`](https://github.com/microsoft/autogen/blob/main/GrpcWorkerAgentRuntime.cs)). It exposes the `IAgentRuntime` contract over gRPC and forwards messages to the local runtime implementation (`InProcessRuntime` for .NET or the Python equivalent).

2. **Agent Registration** – Each language registers its agents with the remote runtime via `add_subscription` and `register_agent` APIs. Subscriptions map a **topic** to an **agent type**, enabling the runtime to route messages correctly.

3. **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 its `AgentId`, and invokes the `on_message` handler.

4. **State and Lifecycle** – Agents can persist state using `save_state` and `load_state` methods. The runtime guarantees graceful shutdown through `stop_when_signal` and `stop_when_idle` methods.

## 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`](https://github.com/microsoft/autogen/blob/main/hello_python_agent.py):

```python
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`](https://github.com/microsoft/autogen/blob/main/HelloAgent.cs) using the `BaseAgent` class and handles protobuf messages from Python:

```csharp
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:

```bash

# 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`](https://github.com/microsoft/autogen/blob/main/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`](https://github.com/microsoft/autogen/blob/main/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`](https://github.com/microsoft/autogen/blob/main/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`](https://github.com/microsoft/autogen/blob/main/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`](https://github.com/microsoft/autogen/blob/main/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`](https://github.com/microsoft/autogen/blob/main/dotnet/samples/Hello/HelloAgent/HelloAgent.cs)** – Sample .NET agent demonstrating `TypeSubscription` and protobuf message handling.
- **[`dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/core_xlang_hello_python_agent/hello_python_agent.py`](https://github.com/microsoft/autogen/blob/main/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`](https://github.com/microsoft/autogen/blob/main/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.