Skip to main content
Version: 1.0.5

Agent Chat Protocol

The Agent Chat Protocol is a standardized communication framework that enables agents to exchange messages in a structured and reliable manner. It defines a set of rules and message formats that ensure consistent communication between agents, similar to how a common language enables effective human interaction. This guide demonstrates how to implement and utilize this protocol in your agents.

Understanding the Chat Protocol

The chat protocol consists of several key components that work together to enable reliable communication between agents. Let's explore each component:

1. Core Models

Schema excerpts

The blocks below are schema excerpts from uagents_core — not standalone runnable files. Prefer importing symbols instead of redefining them. Canonical import:

from uagents_core.contrib.protocols.chat import (
TextContent,
Resource,
ResourceContent,
MetadataContent,
StartSessionContent,
EndSessionContent,
StartStreamContent,
EndStreamContent,
ChatMessage,
ChatAcknowledgement,
chat_protocol_spec,
)

TextContent

class TextContent(Model):
type: Literal['text']
text: str
  • Basic content type for text messages
  • Uses Literal['text'] to ensure type safety
  • text field stores the actual message content

Resource and ResourceContent

class Resource(Model):
uri: str
metadata: dict[str, str]

class ResourceContent(Model):
type: Literal['resource']
resource_id: UUID4
resource: Resource | list[Resource]
  • Resource: Represents external resources (files, images, etc.)
    • uri: Location of the resource
    • metadata: Additional resource information
  • ResourceContent: Wraps resources in messages
    • resource_id: Unique identifier for tracking
    • resource: Single or multiple resources

Metadata Types

class Metadata(TypedDict):
mime_type: str
role: str

class MetadataContent(Model):
type: Literal['metadata']
metadata: dict[str, str]
  • Metadata: Documents conventional keys used in resource-related metadata (for example on Resource.metadata)
    • mime_type: Resource type (e.g., "text/plain")
    • role: Resource's purpose in communication
  • MetadataContent: For sending metadata-only messages; metadata accepts an arbitrary dict[str, str] (including the conventional mime_type / role keys)

Example using the conventional keys:

MetadataContent(
type="metadata",
metadata={"mime_type": "text/plain", "role": "attachment"},
)

2. Session and Stream Management

Advanced content types

StartSessionContent, EndSessionContent, stream types, and resources are part of the protocol but are not required for this tutorial. The working Agent1/Agent2 scripts below use TextContent + acknowledgements only.

Session Control

class StartSessionContent(Model):
type: Literal['start-session']

class EndSessionContent(Model):
type: Literal['end-session']
  • Manages chat session lifecycle
  • StartSessionContent: Initiates new sessions
  • EndSessionContent: Properly terminates sessions

Stream Control

class StartStreamContent(Model):
type: Literal['start-stream']
stream_id: UUID4

class EndStreamContent(Model):
type: Literal['end-stream']
stream_id: UUID4
  • Handles continuous data streams
  • stream_id: Unique identifier for stream tracking

3. Agent Content Type

AgentContent = (
TextContent
| ResourceContent
| MetadataContent
| StartSessionContent
| EndSessionContent
| StartStreamContent
| EndStreamContent
)
  • Combines all possible content types
  • Ensures type safety in message content

4. Message Types

ChatMessage

class ChatMessage(Model):
timestamp: datetime
msg_id: UUID4
content: list[AgentContent]
  • Primary message type for communication
  • timestamp: When message was sent (UTC)
  • msg_id: Unique message identifier
  • content: List of content elements discussed above.

ChatAcknowledgement

class ChatAcknowledgement(Model):
timestamp: datetime
acknowledged_msg_id: UUID4
metadata: dict[str, str] | None = None
  • Confirms message receipt
  • acknowledged_msg_id: References original message
  • Optional metadata for additional information

5. Message Handlers

Signature-only example

The snippet below is a signature-only reference. protocol is created later as Protocol(spec=chat_protocol_spec). See the full Agent1/Agent2 scripts below for runnable code.

@protocol.on_message(ChatMessage)
async def handle_message(ctx: Context, sender: str, msg: ChatMessage):
ctx.logger.info(f"I got a chat message {sender} {msg}")

@protocol.on_message(ChatAcknowledgement)
async def handle_ack(ctx: Context, sender: str, msg: ChatAcknowledgement):
ctx.logger.info(f"I got a chat acknowledgement {sender} {msg}")
  • Process incoming messages
  • Handle acknowledgements

Using the Chat Protocol

This guide targets Agent Chat Protocol v0.3.0 (chat_protocol_spec.version).

Prerequisites

Agentverse-hosted

  • uagents and the chat protocol are provided by the platform — no local pip install required.

Local

  • Python 3.10+ recommended
  • pip install "uagents>=0.22.3" (pulls a compatible uagents_core), or pip install uagents uagents-core if needed
  • Confirm the protocol version:
python -c "from uagents_core.contrib.protocols.chat import chat_protocol_spec; print(chat_protocol_spec.name, chat_protocol_spec.version)"

Expected: AgentChatProtocol 0.3.0.

See also Local Agents.

Import the components you need from uagents_core:

from uagents_core.contrib.protocols.chat import (
ChatMessage,
ChatAcknowledgement,
TextContent,
chat_protocol_spec,
)

Optional helpers: ChatMessage includes helpers such as .text() to collect text blocks from content. Richer metadata/card helpers live under uagents_core.contrib.protocols.chat as well.

Basic Message Flow

This tutorial uses a one-reply pattern so agents do not loop forever:

  1. Agent1 sends a ChatMessage ("Hello from Agent1!") to Agent2
  2. Agent2 sends a ChatAcknowledgement back to Agent1
  3. Agent2 sends one ChatMessage reply ("Hello from Agent2!") to Agent1
  4. Agent1 sends a ChatAcknowledgement back to Agent2 (Agent1 does not send another ChatMessage)
comparison

Primary path: Agentverse-hosted agents

This tutorial uses Agentverse-hosted agents as the primary path. Create two agents on Agentverse, then paste the hosted scripts below into each agent's editor.

Do not include agent.run() on Agentverse

Agentverse runs the agent for you. Omit the if __name__ == '__main__': agent.run() block when pasting into a hosted agent. That block belongs only in the local alternative.

Refer to the Hosted Agents section for detailed Agentverse setup steps.

Agent1 Script (hosted)

Paste this into your Agent1 editor on Agentverse (no run() block):

agent1.py
from uagents import Agent, Protocol, Context

from uagents_core.contrib.protocols.chat import (
ChatAcknowledgement,
ChatMessage,
TextContent,
chat_protocol_spec,
)

agent1 = Agent()

# REQUIRED: paste Agent2's address from its startup logs before starting Agent1
agent2_address = "PASTE_AGENT2_ADDRESS_HERE"

chat_proto = Protocol(spec=chat_protocol_spec)


@agent1.on_event("startup")
async def startup_handler(ctx: Context):
ctx.logger.info(f"My name is {ctx.agent.name} and my address is {ctx.agent.address}")

if agent2_address.startswith("PASTE_") or not agent2_address.startswith("agent"):
ctx.logger.error("Set agent2_address to Agent2's real address before running Agent1")
return

# timestamp/msg_id are optional — ChatMessage defaults to timezone-aware UTC and a new msg_id
initial_message = ChatMessage(
content=[TextContent(type="text", text="Hello from Agent1!")]
)
await ctx.send(agent2_address, initial_message)


@chat_proto.on_message(ChatMessage)
async def handle_message(ctx: Context, sender: str, msg: ChatMessage):
for item in msg.content:
if isinstance(item, TextContent):
ctx.logger.info(f"Received message from {sender}: {item.text}")
# Ack only — do not reply with another ChatMessage (avoids an unbounded loop)
await ctx.send(
sender,
ChatAcknowledgement(acknowledged_msg_id=msg.msg_id),
)


@chat_proto.on_message(ChatAcknowledgement)
async def handle_acknowledgement(ctx: Context, sender: str, msg: ChatAcknowledgement):
ctx.logger.info(
f"Received acknowledgement from {sender} for message: {msg.acknowledged_msg_id}"
)


# publish_manifest=True publishes the protocol manifest so other agents / Agentverse
# can discover that this agent supports the chat protocol. Use True for tutorials
# and discoverable agents; use False only if you intentionally want to hide support.
agent1.include(chat_proto, publish_manifest=True)

Agent2 Script (hosted)

Paste this into your Agent2 editor on Agentverse (no run() block):

agent2.py
from uagents import Agent, Protocol, Context

from uagents_core.contrib.protocols.chat import (
ChatAcknowledgement,
ChatMessage,
TextContent,
chat_protocol_spec,
)

agent2 = Agent()

chat_proto = Protocol(spec=chat_protocol_spec)


@agent2.on_event("startup")
async def startup_handler(ctx: Context):
ctx.logger.info(f"My name is {ctx.agent.name} and my address is {ctx.agent.address}")


@chat_proto.on_message(ChatMessage)
async def handle_message(ctx: Context, sender: str, msg: ChatMessage):
for item in msg.content:
if isinstance(item, TextContent):
ctx.logger.info(f"Received message from {sender}: {item.text}")

await ctx.send(
sender,
ChatAcknowledgement(acknowledged_msg_id=msg.msg_id),
)

# Single reply from Agent2 only (Agent1 acks but does not chat-reply)
response = ChatMessage(
content=[TextContent(type="text", text="Hello from Agent2!")]
)
await ctx.send(sender, response)


@chat_proto.on_message(ChatAcknowledgement)
async def handle_acknowledgement(ctx: Context, sender: str, msg: ChatAcknowledgement):
ctx.logger.info(
f"Received acknowledgement from {sender} for message: {msg.acknowledged_msg_id}"
)


agent2.include(chat_proto, publish_manifest=True)

Running the Agents on Agentverse

Hosted agents are started from the Agentverse UI (not with python agent2.py). Agent1 will not work until agent2_address is replaced with Agent2's real address.

  1. In Agentverse, create two hosted agents (Agent2 and Agent1). See the Hosted Agents guide.
  2. Paste the Agent2 hosted script into the Agent2 editor (do not include if __name__ == '__main__': agent2.run()).
  3. Click Start on Agent2. Start-Agent-2
  4. From Agent2 logs, copy the line with my address is agent1q.... Agent-Address
  5. Paste that address into Agent1: agent2_address = "agent1q...".
  6. Paste the Agent1 hosted script into the Agent1 editor (again without local run()), then click Start. Start-Agent-1
  7. Confirm logs match Expected Output.

Expected Output

When both agents run successfully, you should see logs similar to:

Agent2

INFO: [agent2]: My name is agent2 and my address is agent1q...
INFO: [agent2]: Received message from agent1q...: Hello from Agent1!
INFO: [agent2]: Received acknowledgement from agent1q... for message: <uuid>

Agent2-Logs

Agent1

INFO: [agent1]: My name is agent1 and my address is agent1q...
INFO: [agent1]: Received acknowledgement from agent1q... for message: <uuid>
INFO: [agent1]: Received message from agent1q...: Hello from Agent2!

Agent1-Logs

Success checklist

  • Agent2 received “Hello from Agent1!”
  • Both sides show acknowledgements
  • Agent1 received Agent2’s reply (“Hello from Agent2!”)

Troubleshooting

  • Agent1 shows no reply → Agent2 is not started, or agent2_address was not replaced with Agent2’s real address.
  • ImportError: uagents_core → Install prerequisites (see above) or use Agentverse-hosted agents.
  • Repeated / flooding messages → Both agents are ack+replying on every ChatMessage. Use the tutorial pattern: Agent2 replies once; Agent1 acks only.
  • Hosted agent won’t start / unexpected local server → Remove the local if __name__ == '__main__': agent.run() block from the hosted editor.

Alternative: running locally

To run these agents on your local machine instead of Agentverse:

  1. Create and activate a virtual environment, then install dependencies:
python -m venv venv
# Windows PowerShell: .\venv\Scripts\Activate.ps1
# macOS/Linux: source venv/bin/activate
pip install "uagents>=0.22.3"
  1. Use the hosted script bodies above, but initialise each agent with a port and endpoint, and keep the local run() block at the bottom of each file:
# For agent1
agent1 = Agent(
name="agent1",
port=8000,
endpoint=["http://localhost:8000/submit"],
)

# For agent2
agent2 = Agent(
name="agent2",
port=8001,
endpoint=["http://localhost:8001/submit"],
)

# At the bottom of each file (local only):
if __name__ == "__main__":
agent1.run() # or agent2.run()
  1. Start Agent2 first and keep it running:
python agent2.py
# Windows if needed: py -3.12 agent2.py
  1. Copy Agent2’s address into Agent1’s agent2_address, then start Agent1 in a second terminal:
python agent1.py

Reminders: start Agent2 before Agent1; both must stay running; Agent1 will not work until agent2_address is replaced.

To learn more about setting up and running agents locally, refer to the Local Agents section of our documentation.