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.
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.
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 agentssetup(): Lifecycle hook for initializationhandle_message(): Async callback for inbound messagessend_message(): Sends a message to another agent by addressRunning 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.
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 in UAgents follows a simple model based on messages with:\n
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.
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.
Agent security often flies under the radar until there’s a hard loss. Here are some personal observations when working with UAgents:
Some of the issues I ran into early on:
await for async methods will lead to silent failures or race conditions.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.
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.
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.