---
title: "MQTT: Lightweight Publish/Subscribe Messaging"
source: Panaversity Learn Agentic AI
source_url: https://github.com/panaversity/learn-agentic-ai
licence: Apache-2.0
domain: agentic-ai
subdomain: panaversity-agentic
date_added: 2026-04-25
---

# MQTT: Lightweight Publish/Subscribe Messaging

MQTT (Message Queuing Telemetry Transport) is an OASIS standard messaging protocol built on a **publish/subscribe** model. It is exceptionally lightweight and designed for constrained devices and low-bandwidth, high-latency, or unreliable networks. This makes it a popular choice for Internet of Things (IoT), mobile applications, sensor networks, and increasingly for event-driven communication in distributed agentic systems.

---

## Core MQTT Concepts

1.  **Publish/Subscribe Model**:

    - **Publishers**: Clients that send messages.
    - **Subscribers**: Clients that receive messages.
    - **Broker**: A server that receives messages from publishers and routes them to interested subscribers. Publishers and subscribers are decoupled; they don't know about each other directly.
    - **Topics**: Messages are published to "topics," which are hierarchical strings (e.g., `sensors/home/livingroom/temperature` or `agents/notifications/user123`). Subscribers subscribe to topics they are interested in. Wildcards (`+` for single level, `#` for multi-level) can be used in topic subscriptions.

2.  **Broker**: The central hub in MQTT. All messages pass through the broker. It's responsible for:

    - Receiving messages from publishers.
    - Filtering messages based on topics.
    - Forwarding messages to subscribers that have subscribed to those topics.
    - Managing client connections, sessions, and subscriptions.
    - Handling Quality of Service (QoS) levels.
    - Storing retained messages (if configured).

3.  **Quality of Service (QoS)**: MQTT defines three levels of QoS for message delivery:

    - **QoS 0 (At most once)**: Fire and forget. The message is delivered at most once, or not at all. No acknowledgment. Fastest, but least reliable.
    - **QoS 1 (At least once)**: The message is guaranteed to be delivered at least once. The sender stores the message until it receives an acknowledgment (PUBACK) from the receiver (broker or subscriber). Duplicates are possible if acknowledgment is lost.
    - **QoS 2 (Exactly once)**: The message is guaranteed to be delivered exactly once. This is the most reliable but slowest level, involving a four-part handshake (PUBLISH, PUBREC, PUBREL, PUBCOMP).

4.  **Retained Messages**: When a publisher sends a message with the "retain" flag set to true, the MQTT broker stores this message for that specific topic. When a new client subscribes to that topic, it will immediately receive the last retained message on that topic, even if it was published before the client subscribed. Only one message is retained per topic.

5.  **Last Will and Testament (LWT)**: A client can register an LWT message with the broker when it connects. If the client disconnects ungracefully (e.g., network failure, crash) without sending a proper DISCONNECT packet, the broker will automatically publish the LWT message to the specified LWT topic. This allows other clients to be notified of the unexpected disconnection.

6.  **Clean Session vs. Persistent Session**:

    - **Clean Session (Clean Start flag in MQTTv5)**: If a client connects with `clean_session=True` (or `Clean Start = 1`), the broker discards any previous session information for that client ID (subscriptions, queued messages for QoS 1 & 2 if the client was offline). The client starts fresh.
    - **Persistent Session (`clean_session=False` or `Clean Start = 0`)**: If a client connects with `clean_session=False`, the broker attempts to resume a previous session associated with that client ID. If a session exists, subscriptions are restored, and any QoS 1 or 2 messages queued for the client while it was disconnected are delivered.

7.  **Keep Alive**: An interval (in seconds) defined by the client during connection. If the broker doesn't receive any communication (including PINGREQ packets if no other data is flowing) from the client within 1.5 times the keep-alive period, it may consider the client disconnected and, if an LWT is registered, publish it.

---

## Working with MQTT in Python: `paho-mqtt`

The Eclipse Paho MQTT Python client library (`paho-mqtt`) is a widely used library for connecting to MQTT brokers, publishing messages, and subscribing to topics.

### Installation

```bash
# Using pip
pip install paho-mqtt

# Or using uv
# uv pip install paho-mqtt
```

### MQTT Broker Requirement

To run MQTT examples, you need an MQTT broker. You can:

- Install a local broker like [Mosquitto](https://mosquitto.org/) (available for Linux, macOS, Windows).
- Use a public test broker like `test.mosquitto.org` or `broker.hivemq.com` (be mindful that these are public and not for sensitive data).
- Use a managed cloud MQTT service.

### Example: Combined MQTT Client (Publisher & Subscriber)

This script demonstrates connecting to an MQTT broker, subscribing to a unique topic, publishing a message to that topic, and receiving it. It also shows LWT configuration and basic status publishing.

**File:** `mqtt_combined_client.py`

```python
import paho.mqtt.client as mqtt
import time
import logging
import os
import uuid

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - MQTT_DEMO - %(levelname)s - %(message)s')

# --- Configuration ---
# Use a public broker for ease of testing; for production, use your own secured broker.
MQTT_BROKER_HOST = "test.mosquitto.org"
MQTT_BROKER_PORT = 1883  # Default MQTT port (unencrypted)
# For TLS/SSL, use port 8883 or 8884 typically, and set up client.tls_set()

# Generate a unique client ID to avoid conflicts if multiple instances run
CLIENT_ID = f"daca-mqtt-demo-client-{uuid.uuid4().hex[:6]}"
TOPIC_BASE = "daca/agentic-ai/demo"
TOPIC_TEST = f"{TOPIC_BASE}/{CLIENT_ID}/test" # Unique topic for this client instance

# --- Global variable to signal message reception for the demo ---
message_received_flag = False
received_message_payload = None

# --- Callback functions ---
def on_connect(client, userdata, flags, rc, properties=None):
    """Called when the client connects to the broker."""
    if rc == 0:
        logging.info(f"Successfully connected to MQTT broker {MQTT_BROKER_HOST} with client ID {CLIENT_ID}")
        # Subscribe to the test topic upon successful connection
        # We use QoS 1 for subscription to ensure the subscribe message is acknowledged by the broker.
        (result, mid) = client.subscribe(TOPIC_TEST, qos=1)
        if result == mqtt.MQTT_ERR_SUCCESS:
            logging.info(f"Successfully subscribed to topic '{TOPIC_TEST}' with MID {mid}")
        else:
            logging.error(f"Failed to subscribe to topic '{TOPIC_TEST}'. Result code: {result}")
    else:
        logging.error(f"Failed to connect to MQTT broker. Return code: {rc} ({mqtt.connack_string(rc)})")
        # Handle specific error codes if needed
        if rc == mqtt.MQTT_ERR_CONN_REFUSED:
            logging.error("Connection refused. Check broker address, port, and firewall.")
        elif rc == mqtt.MQTT_ERR_NO_CONN:
            logging.error("No connection to broker. Network issue or broker down?")


def on_disconnect(client, userdata, rc, properties=None):
    """Called when the client disconnects from the broker."""
    logging.info(f"Disconnected from MQTT broker with result code {rc}. Client ID: {CLIENT_ID}")
    if rc != 0:
        logging.warning("Unexpected disconnection.")

def on_subscribe(client, userdata, mid, granted_qos, properties=None):
    """Called when the broker responds to a subscribe request."""
    logging.info(f"Subscription acknowledged by broker. MID: {mid}, Granted QoS: {granted_qos}")

def on_publish(client, userdata, mid, properties=None):
    """Called when a message is successfully published (for QoS > 0)."""
    logging.info(f"Message MID {mid} published successfully.")

def on_message(client, userdata, msg):
    """Called when a message is received from a subscribed topic."""
    global message_received_flag, received_message_payload
    payload_str = msg.payload.decode('utf-8')
    logging.info(f"Received message on topic '{msg.topic}' (QoS {msg.qos}): \"{payload_str}\"")
    message_received_flag = True
    received_message_payload = payload_str
    # In a real application, you would process the message here.

def on_log(client, userdata, level, buf):
    """Paho MQTT internal logging callback."""
    # You can map paho's levels to your logging levels if needed
    # logging.debug(f"PAHO LOG (Level {level}): {buf}")
    pass


# --- Main client logic ---
def run_mqtt_demo():
    global message_received_flag, received_message_payload

    # 1. Create an MQTT client instance
    #    Using MQTTv5 by default if available with paho-mqtt 2.x.
    #    For MQTTv3.1.1 explicitly: client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id=CLIENT_ID, protocol=mqtt.MQTTv311)
    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID, clean_session=True)
    # Note on clean_session:
    # - True: Broker discards session state (subscriptions, queued messages for QoS 1/2) on disconnect.
    # - False: Broker keeps session state. Client must use same Client ID to resume.

    # 2. Assign callback functions
    client.on_connect = on_connect
    client.on_disconnect = on_disconnect
    client.on_subscribe = on_subscribe
    client.on_publish = on_publish
    client.on_message = on_message
    client.on_log = on_log # Optional: for detailed Paho logs

    # Optional: Configure Last Will and Testament (LWT)
    # This message is sent by the broker if the client disconnects ungracefully.
    lwt_topic = f"{TOPIC_BASE}/client_status/{CLIENT_ID}"
    lwt_payload = "offline - ungraceful disconnect"
    client.will_set(lwt_topic, payload=lwt_payload, qos=1, retain=True)
    logging.info(f"Configured LWT: Topic='{lwt_topic}', Payload='{lwt_payload}'")

    try:
        # 3. Connect to the broker
        logging.info(f"Attempting to connect to MQTT broker: {MQTT_BROKER_HOST}:{MQTT_BROKER_PORT} as client {CLIENT_ID}")
        # client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT, keepalive=60)
        # For paho-mqtt 2.x, connect_async is preferred if you are managing the loop elsewhere
        # but for this simple script, connect() with loop_start() is fine.
        client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT, keepalive=60)
        # Keepalive: Max period in seconds between communications. Broker may disconnect client if exceeded.

        # 4. Start the network loop
        #    loop_start() runs a background thread to handle network traffic, callbacks, and reconnections.
        client.loop_start()
        logging.info("MQTT client network loop started.")

        # Wait a bit for connection and subscription to complete
        time.sleep(3) # Adjust as needed, esp. for slower networks/brokers

        if not client.is_connected():
            logging.error("Client failed to connect after waiting. Exiting.")
            client.loop_stop() # Ensure loop is stopped
            return

        # 5. Publish a test message
        message_to_publish = f"Hello from {CLIENT_ID} at {time.ctime()}!"
        # QoS 0: At most once (fire and forget)
        # QoS 1: At least once (acknowledgement required)
        # QoS 2: Exactly once (two-phase acknowledgement)
        # For this demo, using QoS 1 for publish.
        (result, mid) = client.publish(TOPIC_TEST, payload=message_to_publish, qos=1, retain=False)
        # Retain=False: Broker doesn't store this message for new subscribers.
        # Retain=True: Broker stores it as the "last known good" message for this topic.

        if result == mqtt.MQTT_ERR_SUCCESS:
            logging.info(f"Message \"{message_to_publish}\" published to '{TOPIC_TEST}' with MID {mid}. Waiting for echo...")
        else:
            logging.error(f"Failed to publish message to '{TOPIC_TEST}'. Result code: {result}")

        # 6. Wait for the message to be received by our own subscriber
        wait_time = 10  # seconds
        start_wait_time = time.time()
        while not message_received_flag and (time.time() - start_wait_time) < wait_time:
            time.sleep(0.1)

        if message_received_flag:
            logging.info(f"Successfully received our published message: \"{received_message_payload}\"")
        else:
            logging.warning(f"Did not receive the published message on '{TOPIC_TEST}' within {wait_time} seconds.")

        # 7. Publish a "client online" status message (could be retained)
        status_payload = "online"
        client.publish(lwt_topic, payload=status_payload, qos=1, retain=True) # LWT topic also used for status
        logging.info(f"Published status '{status_payload}' to LWT topic '{lwt_topic}' (retained).")

        # Keep the client running for a bit longer to allow for other interactions or manual testing
        logging.info("Client will remain active for another 10 seconds for observation (e.g. with MQTT Explorer). Press Ctrl+C to exit earlier.")
        time.sleep(10)

    except KeyboardInterrupt:
        logging.info("Keyboard interrupt detected. Shutting down.")
    except ConnectionRefusedError:
        logging.error(f"Connection refused by MQTT broker at {MQTT_BROKER_HOST}. Is it running and accessible?")
    except Exception as e:
        logging.error(f"An unexpected error occurred: {e}", exc_info=True)
    finally:
        # 8. Disconnect and stop the loop
        if client.is_connected():
            logging.info("Disconnecting from MQTT broker...")
            # Publish a final "offline" status before clean disconnect
            client.publish(lwt_topic, payload="offline - clean disconnect", qos=1, retain=True)
            time.sleep(0.5) # Give a moment for the message to send
            client.disconnect() # This will trigger on_disconnect
        client.loop_stop() # Stop the network loop thread
        logging.info("MQTT client network loop stopped. Exiting demo.")

if __name__ == "__main__":
    run_mqtt_demo()
```

**To run this client:**

1. Save the code above as `17_MQTT/mqtt_combined_client.py`.
2. Ensure you have an MQTT broker accessible (e.g., `test.mosquitto.org` is used in the script, or run your own Mosquitto instance).
3. Install the `paho-mqtt` library: `pip install paho-mqtt` (or `uv pip install paho-mqtt`).
4. Run from your terminal: `python 17_MQTT/mqtt_combined_client.py`
   The client will connect, subscribe, publish a message to its own topic, receive it, and then publish status messages. You can observe its activity using an MQTT exploration tool (like MQTT Explorer) connected to the same broker, subscribing to `daca/agentic-ai/demo/#`.

---

## Strengths of MQTT

- **Lightweight and Efficient**: Minimal protocol overhead (2-byte fixed header for some packets) results in low network bandwidth usage and reduced power consumption, ideal for battery-powered devices and constrained networks.
- **Publish/Subscribe Decoupling**: Publishers and subscribers are independent. They don't need to know each other's IP addresses or be online simultaneously (for QoS > 0 with persistent sessions).
- **Reliable Message Delivery**: QoS levels 1 and 2 provide guarantees for message delivery, crucial for applications where data loss is unacceptable.
- **Scalability**: A single broker can handle thousands to millions of concurrently connected clients, and brokers can be clustered for higher availability and load distribution (though clustering is not part of the MQTT spec itself and depends on the broker implementation).
- **Last Will and Testament (LWT)**: Provides a mechanism for presence detection and notifying other clients about ungraceful disconnections.
- **Retained Messages**: Allows new subscribers to get the last known good state or value for a topic immediately upon subscribing.
- **Broad Language Support**: MQTT client libraries are available for virtually all programming languages and platforms.
- **Mature Standard**: Well-defined and widely adopted, especially in the IoT domain.

## Weaknesses and Considerations

- **Broker as a Central Point**: The broker can be a single point of failure unless a high-availability broker setup (e.g., clustering) is implemented. This adds complexity.
- **TCP-Based**: Standard MQTT runs over TCP, which provides reliable, ordered delivery but has connection setup overhead. (MQTT-SN exists for UDP/non-TCP networks but is less common).
- **Basic Topic Hierarchy**: While flexible, the topic structure is a simple hierarchy. Complex routing or message filtering based on content requires logic on the client or via external systems processing broker data.
- **Security Model**: MQTT itself defines client authentication (username/password) and can be layered over TLS for encryption. However, fine-grained authorization (which clients can publish/subscribe to which topics) is typically managed by the broker's configuration or ACLs (Access Control Lists).
- **Message Size Limits**: While not strictly defined by the protocol, brokers often impose practical limits on message size to maintain performance.
- **Ordering with Shared Subscriptions**: When multiple subscribers share a subscription (a feature in some brokers or MQTT v5 for load balancing), message order for a specific publisher might not be guaranteed across all instances of the shared subscription.

## Common Use Cases

- **Internet of Things (IoT)**: Sensor data collection (temperature, humidity, location), device control, firmware updates.
- **Mobile Messaging**: Push notifications, chat applications (especially where bandwidth and battery are concerns).
- **Industrial Automation (IIoT)**: Monitoring and controlling machinery, SCADA systems.
- **Home Automation**: Smart home devices communicating status and commands.
- **Healthcare**: Remote patient monitoring, medical device data transmission.
- **Automotive**: Connected car telemetry, vehicle-to-everything (V2X) communication.
- **Logistics and Asset Tracking**: Real-time location updates for fleets and goods.
- **Event-Driven Architectures**: As a lightweight message bus for microservices or distributed applications to communicate events.

## MQTT in DACA and A2A Communication

MQTT is a strong candidate for various communication patterns within the Dapr Agentic Cloud Ascent (DACA) framework, particularly for Agent-to-Agent (A2A) interactions that benefit from its lightweight, publish-subscribe nature.

**How MQTT fits into DACA:**

1.  **Dapr Pub/Sub Building Block**: Dapr's pub/sub API abstracts the underlying message broker. MQTT brokers (like Mosquitto, HiveMQ, Azure IoT Hub, AWS IoT Core) can be configured as Dapr pub/sub components. This means agents in DACA can publish and subscribe to Dapr topics, and Dapr handles the interaction with the MQTT broker, providing a consistent API regardless of the chosen broker.

    - **Example**: An agent publishes an event `agent_A.publishEvent(pubsubName="mqtt_broker", topic="task_completed", data={"taskId":"123"})`. Another agent subscribed to `task_completed` via Dapr would receive this, with Dapr managing the MQTT specifics.

2.  **Direct MQTT for Specific Needs**: While Dapr abstraction is generally preferred for consistency, agents could also use MQTT client libraries directly if they need fine-grained control over MQTT-specific features not exposed by the Dapr pub/sub API (e.g., certain LWT configurations, specific QoS handling not mapped by Dapr).

**When to Choose MQTT for A2A in DACA:**

- **Event Notifications & Decoupled Communication**: Ideal when one agent needs to notify multiple other agents about an event without direct coupling. For example, a sensor agent publishing data, and multiple processing or logging agents subscribing to it.
- **Geographically Distributed Agents/Devices**: For agents running on edge devices, IoT hardware, or in environments with potentially unreliable network connectivity. MQTT's low overhead and QoS make it suitable.
- **Telemetry and Status Updates**: Agents can publish their health, status, or telemetry data to specific MQTT topics, allowing monitoring agents or dashboards to subscribe.
- **Presence Detection**: Using LWT, agents can signal their online/offline status to other interested agents or a central registry.
- **Scenarios Requiring Guaranteed Delivery for Critical Events**: QoS 1 or 2 can be used for A2A messages that must be processed reliably (e.g., critical alerts, commands).
- **Resource-Constrained Agents**: If some DACA agents are running on very lightweight hardware, MQTT's minimal footprint is advantageous.

**Comparison with other DACA communication options:**

- **vs. WebSockets/Streamable HTTP**: WebSockets and Streamable HTTP are better for direct, often request-response or bidirectional streaming interactions between two specific agents. MQTT is more about broadcasting events to potentially many unknown subscribers.
- **vs. gRPC**: gRPC is suited for request-response or streaming RPCs between services, often with Protobuf. MQTT is message-oriented, not RPC-oriented, and typically uses simpler payloads (JSON, plain text, binary).
- **vs. other Message Queues (Kafka, RabbitMQ via Dapr Pub/Sub)**: While Dapr can use Kafka/RabbitMQ as pub/sub backends, MQTT itself is often seen as even more lightweight than AMQP (RabbitMQ) or Kafka, especially on the client-side. The choice might depend on the scale, feature requirements (e.g., Kafka's stream processing), and existing infrastructure.

**Payload Format:**
For A2A communication over MQTT in DACA, agents would agree on a payload format for messages (e.g., JSON, Protobuf, plain text). JSON-RPC could even be a payload within an MQTT message if an RPC-like interaction is desired over the pub/sub mechanism.

**Conclusion for DACA:**
MQTT, especially when leveraged through Dapr's pub/sub building block, provides a powerful, scalable, and resilient mechanism for event-driven A2A communication in DACA. It excels in scenarios requiring decoupling, efficient messaging for distributed or constrained agents, and reliable delivery of notifications and telemetry.

---

## Place in the Protocol Stack

- **Layer**: Application Layer (OSI Layer 7).
- **Above**: Application logic, agent frameworks, Dapr pub/sub components.
- **Below**: Typically TCP/IP. MQTT-SN (Sensor Networks) is a variation designed for UDP and other datagram protocols.

---

## Further Reading

- [MQTT.org](https://mqtt.org/) (Official website with specifications and resources)
- [OASIS MQTT Version 5.0 Standard](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html)
- [OASIS MQTT Version 3.1.1 Standard](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html)
- [Eclipse Paho - Python Client](https://www.eclipse.org/paho/index.php?page=clients/python/index.php) (Documentation for `paho-mqtt`)
- [HiveMQ MQTT Essentials](https://www.hivemq.com/mqtt-essentials/) (Excellent series of articles on MQTT concepts)
- [Mosquitto MQTT Broker](https://mosquitto.org/)
- [Dapr Pub/Sub Building Block](https://docs.dapr.io/developing-applications/building-blocks/pubsub/)
