Fetch.ai UAgents Python Tutorial & Multi-Agent Communication
Introduction to Fetch.ai UAgents
Fetch.ai’s UAgents framework is an open-source Python SDK designed for building autonomous software agents that can communicate and collaborate within multi-agent systems. This ecosystem makes it feasible to prototype decentralized AI interactions, from simple messaging to complex on-chain integrations.
For developers working at the intersection of crypto and AI, the Python-focused UAgents SDK is particularly useful. It leverages async programming patterns and offers a modular API for agent behavior, message routing, and state management. What I find helpful here is the lightweight, extensible architecture that easily integrates with other tools in your blockchain or AI pipeline.
This article covers how to set up a minimal autonomous agent, implement multi-agent communication, and understand underlying protocols—all with runnable code examples.
Prerequisites and Setup
Before jumping in, make sure you have Python 3.9+ installed and pip ready to manage packages. I tested this tutorial with version 0.5.4 of the UAgents SDK (check the latest on PyPI or GitHub).
python3 -m venv uagents-env
source uagents-env/bin/activate
pip install fetchai-uagents
You will also need asyncio (part of the standard library) since agent behaviors use asynchronous message handling.
Given you may want to build agents interacting across chains or local testnets later, ensure you have access to RPC endpoints and have reviewed agent wallet security basics — private keys here can be points of failure if exposed.
Writing Your First UAgent in Python
Let’s start with a simple agent that listens for messages and replies with a greeting. Here's a minimal example of a HelloAgent:
import asyncio
from uagents import Agent
class HelloAgent(Agent):
async def setup(self):
## Called once on start
print(f"Agent {self.address} started.")
async def handle_message(self, message):
print(f"Received message: {message.content} from {message.sender}")
## Reply to the sender
await self.send_message(message.sender, f"Hello back from {self.address}!")
async def main():
agent = HelloAgent()
await agent.start()
## Keep running
await asyncio.Future()
if __name__ == '__main__':
asyncio.run(main())
Breakdown:
Agent: Base class for autonomous agents
setup(): Lifecycle hook for initialization
handle_message(): Async callback for inbound messages
send_message(): Sends a message to another agent by address
Running this script will leave your agent listening indefinitely (hence the asyncio.Future() to block).
This pattern is typical: your custom agent subclasses Agent and adds your domain logic inside handle_message or scheduled tasks.
Understanding UAgent Architecture
UAgents use a wallet-derived address as a unique agent ID. This simple identity allows message routing within the local environment or interoperating with an MCP (Model Context Protocol) server if used for multi-agent coordination across nodes.
Agents run asynchronously, reacting to messages or triggers. Behind the scenes, UAgents handle serialization of messages using protobuf formats and track conversation states if necessary.
In practice, you wire up your agent wallet (Ethereum-style private key or mnemonic) when instantiating an agent. This is critical (and a potential security pain point). If you slip up and hardcode private keys or expose them via logs, the agent's funds or off-chain state can be compromised.
In production, I switched from ephemeral wallets to hardware-secured keys combined with session keys that limit spending and permissions, which drastically reduced attack surface.
Multi-Agent Communication Protocols
Multi-agent communication in UAgents follows a simple model based on messages with:
- Sender address
- Receiver address
- Content payload
- Optional metadata or context
This roughly mimics an RPC or message bus but is fully async and extensible.
There is no built-in consensus or conflict resolution—agents trust messages from authorized peers or connections strictly via identity controls.
This leads us to common communication patterns.
Common patterns:
- Request/Response: Agent A asks for data, Agent B replies.
- Event broadcast: One agent publishes a status; others listen.
- Negotiation protocols: Multi-step interactions encoded in message sequences.
Implementing Agent-to-Agent Messaging
Here’s a more practical example demonstrating two agents exchanging data in the same Python process (for simplicity).
import asyncio
from uagents import Agent
class PingAgent(Agent):
async def setup(self):
print(f"PingAgent {self.address} started.")
## Ping 'pong_agent' after a delay
await asyncio.sleep(1)
await self.send_message('pong_agent', 'ping')
async def handle_message(self, message):
print(f"PingAgent received: {message.content}")
class PongAgent(Agent):
async def setup(self):
print(f"PongAgent {self.address} started.")
async def handle_message(self, message):
print(f"PongAgent received: {message.content}")
if message.content == 'ping':
await self.send_message('ping_agent', 'pong')
async def main():
ping = PingAgent(address='ping_agent') # Using string addresses for demo
pong = PongAgent(address='pong_agent')
await asyncio.gather(ping.start(), pong.start())
## Run for a short period to see the interaction
await asyncio.sleep(3)
if __name__ == '__main__':
asyncio.run(main())
Output:
PingAgent ping_agent started.
PongAgent pong_agent started.
PongAgent received: ping
PingAgent received: pong
The key here is how easy it is to model asynchronous message passing with minimal boilerplate. This example uses string addresses for clarity, but real UAgents usually identify by wallet-derived addresses.
Security Considerations for Autonomous Agents
Agent security often flies under the radar until there’s a hard loss. Here are some personal observations when working with UAgents:
- Private key handling: Never embed raw private keys in code. Use environment variables or secure vaults. Even then, rotate keys regularly.
- Session keys and spending limits: Crucial when agents control on-chain assets. Don't give unlimited approval or fund access.
- Untrusted communication: Messages can be spoofed if transport layers aren’t secure. Use signed messages or encrypted payloads where possible.
- MCP server trust: If your multi-agent setup relies on an MCP server, remind yourself this is a trust boundary. Verify the implementation and audit their code or behavior.
Debugging and Common Pitfalls
Some of the issues I ran into early on:
- Agent address mismatch: If you hardcode agent addresses, ensure they align with wallet keys. Otherwise, messages won't route.
- Async context errors: Forgetting
await for async methods will lead to silent failures or race conditions.
- Agent startup ordering: Agents expecting incoming messages should be started before the senders initiate communication.
- Message content format: Stick to JSON or protobuf formats consistently. Mixing raw strings or different serializations breaks parsing.
Logging in UAgents defaults to INFO level. For debugging, bump it to DEBUG:
import logging
logging.basicConfig(level=logging.DEBUG)
This shows message exchanges, internal state changes, and transport layers.
Comparison With Other Agent Frameworks
If you’re exploring options beyond Fetch.ai UAgents for your python autonomous agents, you might consider:
| Framework |
Language(s) |
Chain Support |
License |
Maturity |
Notes |
| Fetch.ai UAgents |
Python |
EVM chains primarily |
Apache 2.0 |
Early-stage |
Good async model, modular |
| ElizaOS |
TypeScript, Python |
EVM, Solana |
MIT |
Beta |
Agent orchestration + MCP synergy |
| GOAT SDK |
Rust, TypeScript |
Multiple EVM-based |
Apache 2.0 |
Alpha |
Focus on MEV and DeFAI bots |
Each tool targets slightly different use cases—like adding MCP server integration for on-chain agent communication persistence or plugging into trading pipelines ([trading-bot-development]) for agent-driven ops.
Conclusion and Next Steps
By now, you should have a working Python UAgent capable of sending and receiving messages in a multi-agent environment. The example shown here is just the foundation. In practice, you will wire these agents with wallet security practices, asynchronous event handling, and possibly on-chain contract calls.
What I've found valuable is starting small: first get the message passing right, then layer in spending limits and RPC integrations.
Next, consider exploring how to connect these agents to on-chain triggers or model payment flows using protocols like ERC-4337 ([erc-4337-account-abstraction]). This can boost your agent from a standalone bot to a fully autonomous entity operating within broader DeAI or DePIN systems.
For broader context, check out our guides on agent wallet security, comparing framework options, and advanced MCP server integrations.
Curious to see what happens when you combine Fetch.ai UAgents with on-chain AI agents? That’s a great next project.
Happy building! And if you hit any errors, review async task management carefully or check GitHub issues on the UAgents SDK for recent fixes.