# Handling Inter-Agent Message Passing and Communication in MetaGPT

> Learn how MetaGPT handles inter-agent message passing and communication using a publish/subscribe pattern and address-based routing. Discover broadcast and self-routing capabilities.

- Repository: [FoundationAgents/MetaGPT](https://github.com/FoundationAgents/MetaGPT)
- Tags: internals
- Published: 2026-03-04

---

**MetaGPT implements inter-agent message passing through a publish/subscribe pattern where the Environment routes Message objects to Roles using an address-based routing table, supporting broadcast via MESSAGE_ROUTE_TO_ALL and self-routing via MESSAGE_ROUTE_TO_SELF.**

MetaGPT is a multi-agent framework where autonomous agents modeled as Roles collaborate within a shared Environment. Effective coordination requires robust inter-agent message passing and communication mechanisms that route information between specialized agents. This guide examines the concrete implementation in the FoundationAgents/MetaGPT repository, covering message routing, address registration, and the publish/subscribe flow.

## Architectural Overview

MetaGPT models multi-agent systems using three core abstractions. **Roles** represent autonomous agents with specific capabilities and goals. The **Environment** acts as a message broker that maintains a registry of all active roles. **Message** objects carry content, metadata, and explicit routing instructions via the `send_to` field.

The concrete implementation resides in `metagpt.environment.Environment`, which inherits from `ExtEnv` and `BaseEnvironment`. This class manages the lifecycle of messages and ensures delivery to appropriate recipients based on address matching.

## The Environment Routing Table

The Environment maintains a routing table called `member_addrs` that maps each Role to its set of valid addresses. This dictionary enables efficient message delivery without broadcasting to every agent in the system.

```python

# metagpt/environment/base_env.py – member_addrs definition

member_addrs: Dict[BaseRole, Set] = Field(default_factory=dict, exclude=True)

```

When a Role joins the Environment, its addresses are registered in this table. The `member_addrs` mapping allows the Environment to iterate over all registered roles and determine which ones should receive a specific message based on address intersection with the message's `send_to` field.

## Message Routing Constants and Logic

MetaGPT defines special routing constants in [`metagpt/const.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/const.py) to handle common addressing patterns. These constants simplify broadcast and self-referential messaging.

```python

# metagpt/const.py – routing constants

MESSAGE_ROUTE_TO_ALL = "<all>"   # Broadcast to every role

MESSAGE_ROUTE_TO_SELF = "<self>" # Route back to the sender

```

The routing decision logic resides in `is_send_to`, a helper function in [`metagpt/utils/common.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/utils/common.py). This function checks if a message's recipients intersect with a role's address set, returning `True` if the role should receive the message.

```python

# metagpt/utils/common.py – routing predicate

def is_send_to(message: "Message", addresses: set):
    if MESSAGE_ROUTE_TO_ALL in message.send_to:
        return True
    for i in addresses:
        if i in message.send_to:
            return True
    return False

```

## Publishing Messages to Agents

When a Role or external code calls `publish_message`, the Environment iterates through `member_addrs` and delivers the message to matching roles via `put_message`.

```python

# metagpt/environment/base_env.py – publish_message implementation

for role, addrs in self.member_addrs.items():
    if is_send_to(message, addrs):
        role.put_message(message)
        found = True

```

If no recipient matches the message's routing instructions, the Environment logs a warning but still archives the message in its `history` buffer for debugging purposes. This ensures that message flows remain traceable even when routing fails.

## Role-Side Message Handling

Roles receive messages through `put_message`, which enqueues them in the role's private message buffer. This buffer acts as an inbox that the role processes during its observation phase.

```python

# metagpt/roles/role.py – put_message implementation

def put_message(self, message):
    if not message:
        return
    self.rc.msg_buffer.push(message)

```

When a Role needs to emit a message, it uses its own `publish_message` method. This method resolves special routing tags like `MESSAGE_ROUTE_TO_SELF` before forwarding to the Environment.

```python

# metagpt/roles/role.py – role-side publish_message

def publish_message(self, msg):
    if MESSAGE_ROUTE_TO_SELF in msg.send_to:
        msg.send_to.add(any_to_str(self))
        msg.send_to.remove(MESSAGE_ROUTE_TO_SELF)
    ...
    self.rc.env.publish_message(msg)

```

The Role's `_observe` method later consumes messages from `rc.msg_buffer`, transferring them to `rc.memory` and setting `rc.news` for the current action cycle.

## Address Registration Flow

Address registration occurs when a Role joins an Environment. The `set_env` method establishes the bidirectional link between Role and Environment, triggering address registration.

```python

# metagpt/roles/role.py – environment registration

def set_env(self, env):
    self.rc.env = env
    if env:
        env.set_addresses(self, self.addresses)

```

The Environment's `set_addresses` method updates the routing table with the Role's address set, making the Role reachable via any of its registered identifiers.

```python

# metagpt/environment/base_env.py – address registration

def set_addresses(self, obj, addresses):
    self.member_addrs[obj] = addresses

```

This registration mechanism allows Roles to define multiple addresses (name, profile, custom tags) and receive messages directed to any of them.

## Complete Working Example

The following example demonstrates a Product Manager and Engineer exchanging messages within an Environment:

```python
from metagpt.environment import Environment
from metagpt.roles import Role, ProductManager, Engineer
from metagpt.schema import UserMessage
from metagpt.actions import UserRequirement
from metagpt.utils.common import any_to_str

# 1️⃣ Create the environment

env = Environment()

# 2️⃣ Add two roles (they automatically register their addresses)

pm = ProductManager(name="Alice", profile="product manager")
eng = Engineer(name="Bob", profile="engineer")
env.add_roles([pm, eng])

# 3️⃣ Send a user requirement to the product manager only

msg = UserMessage(
    content="We need a search feature powered by LLM",
    cause_by=UserRequirement,
    send_to=any_to_str(pm)   # resolves to the role's address string

)
env.publish_message(msg)

# 4️⃣ Run the environment – each role will observe, think and act.

#    The product manager will receive the message, put it in its buffer,

#    react, and eventually publish a response that the engineer will see.

await env.run(k=2)

```

Under the hood, the Environment resolves the `send_to` address using `is_send_to`, delivers the message to the Product Manager's `msg_buffer` via `put_message`, and logs the exchange in `history`. When `env.run()` executes, the Product Manager observes the message, processes it through its action cycle, and publishes new messages that the Environment routes to the Engineer.

## Key Source Files

| File | Description | Link |
|------|-------------|------|
| [`metagpt/environment/base_env.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/environment/base_env.py) | Core `Environment` implementation, routing table, `publish_message`, `run`, address helpers. | [view](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/environment/base_env.py) |
| [`metagpt/roles/role.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/roles/role.py) | `Role` base class, `publish_message`, `put_message`, environment registration. | [view](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/roles/role.py) |
| [`metagpt/schema.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/schema.py) | `Message` model, default routing fields (`send_to`, `cause_by`, etc.). | [view](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/schema.py) |
| [`metagpt/utils/common.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/utils/common.py) | Helper `is_send_to`, address conversion utilities (`any_to_str`, `any_to_str_set`). | [view](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/utils/common.py) |
| [`metagpt/const.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/const.py) | Routing constants (`MESSAGE_ROUTE_TO_ALL`, `MESSAGE_ROUTE_TO_SELF`). | [view](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/const.py) |

## Summary

- **MetaGPT** implements inter-agent communication through a lightweight publish/subscribe pattern managed by the `Environment` class.
- The **`member_addrs`** routing table in [`metagpt/environment/base_env.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/environment/base_env.py) maps each `Role` to its address set, enabling targeted message delivery.
- **Special routing constants** `MESSAGE_ROUTE_TO_ALL` and `MESSAGE_ROUTE_TO_SELF` support broadcast and self-referential messaging without hardcoding addresses.
- The **`is_send_to`** predicate in [`metagpt/utils/common.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/utils/common.py) determines message eligibility by checking address intersection or broadcast flags.
- **Roles receive messages** via `put_message`, which pushes to `rc.msg_buffer`, and emit messages via `publish_message`, which resolves routing tags before delegating to the Environment.

## Frequently Asked Questions

### How does MetaGPT route a message to specific agents only?

MetaGPT routes messages using the `is_send_to` function in [`metagpt/utils/common.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/utils/common.py). This function checks if the intersection between the message's `send_to` set and the role's registered addresses is non-empty. If the message contains `MESSAGE_ROUTE_TO_ALL`, it matches every role. Otherwise, only roles with overlapping addresses receive the message via `put_message`.

### What is the difference between MESSAGE_ROUTE_TO_ALL and MESSAGE_ROUTE_TO_SELF?

`MESSAGE_ROUTE_TO_ALL` (defined as `"<all>"` in [`metagpt/const.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/const.py)) broadcasts the message to every registered role in the Environment. `MESSAGE_ROUTE_TO_SELF` (defined as `"<self>"`) routes the message back to the sender. When a role calls `publish_message` with `MESSAGE_ROUTE_TO_SELF`, the method automatically replaces the constant with the role's own address string using `any_to_str(self)` before forwarding to the Environment.

### How do roles receive and process incoming messages?

Roles receive messages through the `put_message` method defined in [`metagpt/roles/role.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/roles/role.py). This method pushes the message onto `self.rc.msg_buffer`, which acts as an inbox. During the role's execution cycle, the `_observe` method consumes this buffer, transferring messages to `rc.memory` and setting `rc.news` for the current action iteration. This decouples message receipt from processing, allowing asynchronous communication.

### Where is the routing table stored and how is it populated?

The routing table is stored in the `member_addrs` attribute of the Environment class, defined in [`metagpt/environment/base_env.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/environment/base_env.py) as `Dict[BaseRole, Set]`. It is populated when roles are added to the environment via `add_role` or `add_roles`. The `set_env` method in [`metagpt/roles/role.py`](https://github.com/FoundationAgents/MetaGPT/blob/main/metagpt/roles/role.py) establishes the bidirectional link and calls `env.set_addresses(self, self.addresses)`, which updates `member_addrs` with the role's address set.