Quickstart: Strands Multi-Agent Communication
Build two Strands agents that communicate through Dapr pub/sub. One agent generates content and publishes it, the other subscribes and reviews it. This pattern enables decoupled, scalable multi-agent architectures.
What You'll Build
- Writer Agent: Generates content using Strands and publishes drafts to a topic
- Editor Agent: Subscribes to drafts and provides editorial feedback
Both agents run as separate services, communicating asynchronously through Dapr's pub/sub building block.
Why Async Agent Communication?
Asynchronous pub/sub messaging makes your multi-agent systems production-ready:
- Resiliency — If an agent crashes or restarts, messages wait in the broker until it recovers. No lost work.
- Independent scaling — Scale writer agents and editor agents separately based on load.
- Loose coupling — Agents only know about topics, not each other. Add, remove, or replace agents without breaking the system.
- Guaranteed delivery — The message broker handles retries, dead-letter queues, and at-least-once delivery.
Bring Your Own Broker
This quickstart uses Redis, but Dapr supports 40+ pub/sub brokers including Kafka, RabbitMQ, AWS SNS/SQS, Azure Service Bus, and GCP Pub/Sub. Switch brokers by changing a YAML file — your agent code stays the same.
Prerequisites
Before you begin, ensure you have:
- Dapr CLI installed and initialized (
dapr init) - Python 3.11+
- An AWS account with Bedrock access or OpenAI API key
Setup
Create Project Structure
Create a new project directory with the following structure:
mkdir strands-pubsub-agents && cd strands-pubsub-agents
mkdir -p components
Your project will have this structure:
strands-pubsub-agents/
├── components/
│ └── pubsub.yaml
├── writer_agent.py
├── editor_agent.py
└── requirements.txt
Configure Dapr Pub/Sub Component
Create components/pubsub.yaml:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
This uses Redis as the message broker. Dapr initializes Redis automatically with dapr init.
Install Dependencies
Create requirements.txt:
strands-agents>=0.1.0
dapr>=1.14.0
flask>=3.0.0
cloudevents>=1.11.0
Set up a virtual environment and install:
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
Configure Your Model Provider
For AWS Bedrock (default for Strands):
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_REGION="us-east-1"
Or for OpenAI:
export OPENAI_API_KEY="your-api-key-here"
How It Works
┌─────────────────┐ Dapr Pub/Sub ┌─────────────────┐
│ Writer Agent │ ──────────────────▶ │ Editor Agent │
│ (Strands) │ "content-drafts" │ (Strands) │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
Generates content Subscribes to topic
Publishes draft Receives draft
Provides feedback
- Writer Agent receives a topic via HTTP, uses Strands to generate content, and publishes the draft
- Dapr handles message routing through the configured pub/sub component (Redis)
- Editor Agent subscribes to the topic, receives drafts, and uses Strands to provide editorial feedback
Build the Agents
Create the Writer Agent
Create writer_agent.py:
import json
from flask import Flask, request, jsonify
from dapr.clients import DaprClient
from strands import Agent
app = Flask(__name__)
# Create the Strands Writer Agent
writer = Agent(
system_prompt="""You are a professional content writer.
When given a topic, write a clear, engaging paragraph about it.
Keep your writing concise but informative."""
)
def publish_draft(topic: str, content: str):
"""Publish draft content to Dapr pub/sub."""
with DaprClient() as client:
client.publish_event(
pubsub_name="pubsub",
topic_name="content-drafts",
data=json.dumps({
"topic": topic,
"draft": content
}),
data_content_type="application/json"
)
print(f"Published draft on: {topic}")
@app.route("/write", methods=["POST"])
def write():
"""Endpoint to trigger content generation."""
data = request.json
topic = data.get("topic", "artificial intelligence")
# Generate content using Strands agent
response = writer(f"Write a paragraph about: {topic}")
content = str(response)
# Publish draft via Dapr pub/sub
publish_draft(topic, content)
return jsonify({
"status": "published",
"topic": topic,
"message": "Draft published to editor agent"
})
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(port=5001)
Create the Editor Agent
Create editor_agent.py:
import json
from flask import Flask, request, jsonify
from strands import Agent
app = Flask(__name__)
# Store received reviews
reviews = []
# Create the Strands Editor Agent
editor = Agent(
system_prompt="""You are a professional editor.
Review content and provide constructive feedback.
Focus on clarity, engagement, and accuracy.
Provide 2-3 specific suggestions for improvement."""
)
@app.route("/dapr/subscribe", methods=["GET"])
def subscribe():
"""Tell Dapr which topics this service subscribes to."""
subscriptions = [
{
"pubsubname": "pubsub",
"topic": "content-drafts",
"route": "/receive-draft"
}
]
return jsonify(subscriptions)
@app.route("/receive-draft", methods=["POST"])
def receive_draft():
"""Handle incoming drafts from pub/sub."""
event = request.json
data = event.get("data", {})
if isinstance(data, str):
data = json.loads(data)
topic = data.get("topic", "Unknown")
draft = data.get("draft", "")
print(f"\n{'='*50}")
print(f"Received draft on: {topic}")
print(f"{'='*50}\n")
# Review the draft using Strands agent
response = editor(f"""Review this draft about "{topic}" and provide feedback:
{draft}""")
review = {
"topic": topic,
"original_draft": draft,
"feedback": str(response)
}
reviews.append(review)
print(f"\n{'='*50}")
print(f"Review completed for: {topic}")
print(f"{'='*50}\n")
return jsonify({"status": "SUCCESS"})
@app.route("/reviews", methods=["GET"])
def get_reviews():
"""Retrieve all editorial reviews."""
return jsonify(reviews)
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "healthy"})
if __name__ == "__main__":
app.run(port=5002)
Run the Agents
Start the Editor Agent
Open a terminal and run:
dapr run --app-id editor-agent \
--app-port 5002 \
--dapr-http-port 3501 \
--resources-path ./components \
-- python editor_agent.py
This agent subscribes to the content-drafts topic and waits for messages.
Start the Writer Agent
Open a second terminal, activate the virtual environment, and run:
dapr run --app-id writer-agent \
--app-port 5001 \
--dapr-http-port 3500 \
--resources-path ./components \
-- python writer_agent.py
Trigger Content Generation
Open a third terminal and send a write request:
curl -X POST http://localhost:5001/write \
-H "Content-Type: application/json" \
-d '{"topic": "The future of serverless computing"}'
Watch the terminal outputs:
- Writer Agent: Generates content and publishes the draft
- Editor Agent: Receives draft via pub/sub and generates editorial feedback
View Editorial Reviews
Retrieve all reviews from the Editor Agent:
curl http://localhost:5002/reviews
Key Concepts
| Concept | Description |
|---|---|
| Pub/Sub Decoupling | Agents don't need to know each other's addresses. They communicate through topics. |
| Async Communication | Writer agent publishes and returns immediately. Editor agent processes asynchronously. |
| Scalability | Multiple editor agents can subscribe to the same topic for parallel processing. |
| Reliability | Dapr handles message delivery, retries, and dead-letter queues. |
Next Steps
- Add tools to your Strands agents for external capabilities
- Use Dapr state management to persist agent memory
- Deploy to Kubernetes with Catalyst
- Explore Dapr workflows for orchestrated agent coordination
Clean Up
Stop both Dapr applications:
dapr stop --app-id writer-agent
dapr stop --app-id editor-agent