WhatsApp + AutonomousOrchestrator Integration¶
Complete guide for connecting WhatsApp as an input channel to AI-Parrot's AutonomousOrchestrator via the WhatsApp Bridge and Redis Pub/Sub.
Architecture¶
graph LR
U["📱 WhatsApp User"] -->|message| WA[WhatsApp Cloud]
WA -->|whatsmeow| B["🔌 WhatsApp Bridge<br/>(Go Service)"]
B -->|PUBLISH| R[(Redis Pub/Sub<br/>whatsapp:messages)]
R -->|SUBSCRIBE| H["WhatsAppRedisHook"]
H -->|HookEvent| O["AutonomousOrchestrator"]
O -->|ask / run_flow| A["Agent / Crew"]
A -->|response| O
O -->|send_reply| H
H -->|HTTP POST /send| B
B -->|whatsmeow| WA
WA -->|reply| U
Message Flow¶
- User sends WhatsApp message → Bridge receives via
whatsmeow - Bridge publishes
IncomingMessageJSON to Redis channelwhatsapp:messages WhatsAppRedisHooksubscribes, filters, routes → emitsHookEventAutonomousOrchestrator._handle_hook_event()createsExecutionRequest- Agent/Crew processes the task, returns response
- If
auto_reply=True, response is POSTed back via Bridge/sendendpoint
Prerequisites¶
| Component | Purpose | Default |
|---|---|---|
| Redis 7+ | Message broker (Pub/Sub) | redis://localhost:6379 |
| WhatsApp Bridge | Go service (whatsmeow) | http://localhost:8765 |
| AI-Parrot | Python orchestration | http://localhost:8000 |
1. Infrastructure Setup¶
Option A: Docker Compose (recommended)¶
The project includes a ready-to-use docker-compose.yml:
# Start all services (Redis + Bridge + App)
docker compose up -d
# View Bridge logs (QR code will appear here)
docker compose logs -f whatsapp-bridge
Services defined in docker-compose.yml:
| Service | Container | Port | Notes |
|---|---|---|---|
redis |
parrot-redis |
6379 | redis:7-alpine with AOF persistence |
whatsapp-bridge |
parrot-whatsapp-bridge |
8765 | Auto-builds from services/whatsapp-bridge/ |
ai-parrot |
parrot-app |
8000 | Connects to Redis + Bridge |
Option B: Makefile Targets (local dev)¶
# Build Bridge binary from Go source
make build-whatsapp-bridge
# Run Bridge locally (Redis must be running separately)
make run-whatsapp-bridge
# Or build and run via Docker
make docker-whatsapp-bridge
Option C: Manual Setup¶
# Terminal 1: Redis
docker run -p 6379:6379 redis:7-alpine
# Terminal 2: WhatsApp Bridge
cd services/whatsapp-bridge
go mod download
go run main.go
# Terminal 3: Your Python application
source .venv/bin/activate
python examples/whatsapp_orchestrator.py
2. WhatsApp Authentication¶
The Bridge authenticates via QR code scanning (first time only — session persists in SQLite):
Web QR (recommended)¶
Open http://localhost:8765/qr in your browser.
A page with the QR code and live authentication status appears.
Terminal QR¶
The QR code also prints to the terminal where the Bridge runs.
Verification¶
Expected response when authenticated:
3. Hook Configuration¶
WhatsAppRedisHookConfig¶
Defined in parrot/autonomous/hooks/models.py:
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
"whatsapp_hook" |
Hook instance name |
enabled |
bool |
True |
Enable/disable |
target_type |
str |
"agent" |
Default target: "agent" or "crew" |
target_id |
str |
None |
Default agent/crew name |
redis_url |
str |
"redis://localhost:6379" |
Redis connection URL |
channel |
str |
"whatsapp:messages" |
Redis Pub/Sub channel |
bridge_url |
str |
"http://localhost:8765" |
Bridge HTTP URL |
auto_reply |
bool |
True |
Send responses back via WhatsApp |
command_prefix |
str |
"" |
Required message prefix (e.g., "!", "/") |
allowed_phones |
List[str]? |
None |
Phone whitelist (None = allow all) |
allowed_groups |
List[str]? |
None |
Group name whitelist |
routes |
List[Dict]? |
None |
Routing rules (see below) |
Routing Rules¶
Routes are checked in order (first match wins). Each route is a dict:
{
"name": "sales", # Human-readable label
"keywords": ["precio", "comprar"], # Match if any keyword in message
"phones": ["14155551234"], # Match if sender phone matches
"target_id": "SalesAgent", # Override target agent/crew
"target_type": "agent" # Override target type
}
- Phone match has higher priority than keyword match within the same route.
- If no route matches, the default
target_idis used.
Factory Helpers¶
Three convenience functions in parrot/autonomous/hooks/models.py:
from parrot.autonomous.hooks import (
create_simple_whatsapp_hook,
create_multi_agent_whatsapp_hook,
create_crew_whatsapp_hook,
)
# Single agent — all messages go to one agent
config = create_simple_whatsapp_hook("CustomerService", allowed_phones=["14155552671"])
# Multi-agent — route by keywords or phone numbers
config = create_multi_agent_whatsapp_hook(
default_agent="GeneralAgent",
routes=[
{"name": "sales", "keywords": ["precio", "buy"], "target_id": "SalesAgent"},
{"name": "vip", "phones": ["14155551234"], "target_id": "VIPAgent"},
],
)
# Crew — route to an AgentCrew
config = create_crew_whatsapp_hook("research_team", command_prefix="!")
4. Python Usage Examples¶
Example 1: Single Agent¶
"""Minimal WhatsApp → single agent setup."""
import asyncio
from parrot.autonomous.orchestrator import AutonomousOrchestrator
from parrot.autonomous.hooks import WhatsAppRedisHook, create_simple_whatsapp_hook
from parrot.registry import AgentRegistry
from parrot.bots.agent import BasicAgent
async def main():
# 1. Create and register agent
agent = BasicAgent(
name="CustomerService",
llm="google:gemini-3.1-flash-lite-preview",
system_instructions="You are a helpful customer service agent. Be concise.",
)
await agent.configure()
registry = AgentRegistry()
registry.register_bot(agent)
# 2. Create orchestrator
orchestrator = AutonomousOrchestrator(
agent_registry=registry,
redis_url="redis://localhost:6379",
)
# 3. Configure and register WhatsApp hook
config = create_simple_whatsapp_hook(
agent_name="CustomerService",
allowed_phones=["14155552671"],
command_prefix="!",
)
hook = WhatsAppRedisHook(config=config)
orchestrator.add_hook(hook)
# 4. Start (blocks until stopped)
await orchestrator.start()
print("Orchestrator running. Send WhatsApp messages like: !hello")
try:
await asyncio.Event().wait() # Run forever
finally:
await orchestrator.stop()
if __name__ == "__main__":
asyncio.run(main())
Example 2: Multi-Agent with Routing¶
"""Route WhatsApp messages to different agents by keywords."""
import asyncio
from parrot.autonomous.orchestrator import AutonomousOrchestrator
from parrot.autonomous.hooks import WhatsAppRedisHook, create_multi_agent_whatsapp_hook
from parrot.registry import AgentRegistry
from parrot.bots.agent import BasicAgent
async def main():
registry = AgentRegistry()
# Create specialized agents
for name, instructions in [
("SalesAgent", "You handle pricing and purchase inquiries."),
("SupportAgent", "You handle technical support and troubleshooting."),
("GeneralAgent", "You handle general questions."),
]:
agent = BasicAgent(
name=name,
llm="google:gemini-3.1-flash-lite-preview",
system_instructions=instructions,
)
await agent.configure()
registry.register_bot(agent)
# Multi-agent routing
config = create_multi_agent_whatsapp_hook(
default_agent="GeneralAgent",
routes=[
{
"name": "sales",
"keywords": ["precio", "comprar", "price", "buy"],
"target_id": "SalesAgent",
"target_type": "agent",
},
{
"name": "support",
"keywords": ["ayuda", "error", "help", "bug"],
"target_id": "SupportAgent",
"target_type": "agent",
},
],
)
orchestrator = AutonomousOrchestrator(
agent_registry=registry,
redis_url="redis://localhost:6379",
)
orchestrator.add_hook(WhatsAppRedisHook(config=config))
await orchestrator.start()
print("Multi-agent router running:")
print(" 'precio del plan' → SalesAgent")
print(" 'necesito ayuda' → SupportAgent")
print(" 'hola mundo' → GeneralAgent")
try:
await asyncio.Event().wait()
finally:
await orchestrator.stop()
if __name__ == "__main__":
asyncio.run(main())
Example 3: Crew Execution¶
"""Route WhatsApp messages to an AgentCrew."""
import asyncio
from parrot.autonomous.orchestrator import AutonomousOrchestrator
from parrot.autonomous.hooks import WhatsAppRedisHook, create_crew_whatsapp_hook
from parrot.manager import BotManager
async def main():
# BotManager handles crew registrations
bot_manager = BotManager()
# Register a crew (assumes crew YAML or programmatic setup)
# await bot_manager.register_crew("research_team", crew_definition)
config = create_crew_whatsapp_hook(
crew_id="research_team",
command_prefix="!",
)
orchestrator = AutonomousOrchestrator(
bot_manager=bot_manager,
redis_url="redis://localhost:6379",
)
orchestrator.add_hook(WhatsAppRedisHook(config=config))
await orchestrator.start()
print("Crew router running. Send: !investigate quantum computing")
try:
await asyncio.Event().wait()
finally:
await orchestrator.stop()
if __name__ == "__main__":
asyncio.run(main())
5. The HookEvent Payload¶
When WhatsAppRedisHook receives a message, it emits a HookEvent with this payload structure:
{
# User identification
"from": "14155552671",
"from_name": "John Doe",
"user_id": "14155552671",
# Message content
"content": "hello, I need help", # After prefix stripping
"original_content": "!hello, I need help", # Original message
"message_id": "3EB0A1B2C3D4E5F6",
# Group info
"is_group": False,
"group_name": "",
# Session management
"session_id": "whatsapp_14155552671", # Per-phone sessions
# For groups: "whatsapp_group_{name}_{phone}"
# Routing
"matched_route": "support", # Or None if default
# Auto-reply config
"reply_via_bridge": True,
"bridge_config": {
"phone": "14155552671",
"bridge_url": "http://localhost:8765",
"auto_reply": True,
},
# Raw data from Bridge
"raw_data": { ... }, # Full IncomingMessage JSON
}
6. Bridge API Reference¶
The Go Bridge exposes these HTTP endpoints:
GET /health¶
Returns connection and authentication status.
POST /send¶
Send a WhatsApp message:
curl -X POST http://localhost:8765/send \
-H "Content-Type: application/json" \
-d '{"phone": "14155552671", "message": "Hello from AI!"}'
Response:
GET /qr¶
Interactive HTML page with QR code and WebSocket-based auth status.
GET /qr.png¶
Raw QR code image (PNG).
WebSocket /ws¶
Real-time QR code and authentication events.
7. Redis Message Format¶
The Bridge publishes to whatsapp:messages with this JSON schema (IncomingMessage struct from main.go):
{
"from": "14155552671",
"from_name": "John Doe",
"content": "Hello!",
"type": "text",
"media": "",
"timestamp": 1739577200,
"message_id": "3EB0A1B2C3D4E5F6",
"is_group": false,
"group_name": "",
"extra": {}
}
Supported type values: text, image, audio, video, document, unknown.
Note: The hook currently only processes
type: "text"messages only.
8. Debugging¶
Monitor Redis messages¶
Check orchestrator stats¶
stats = orchestrator.get_stats()
print(stats["components"]["hooks"])
# {"total": 1, "enabled": 1, "by_type": {"whatsapp_redis": 1}}
Enable debug logging¶
Verify Bridge connectivity from Python¶
import aiohttp
async def check_bridge():
async with aiohttp.ClientSession() as session:
async with session.get("http://localhost:8765/health") as resp:
data = await resp.json()
print(f"Connected: {data['data']['connected']}")
print(f"Authenticated: {data['data']['authenticated']}")
9. Environment Variables¶
| Variable | Default | Description |
|---|---|---|
REDIS_URL / REDIS_SERVICES_URL |
redis://localhost:6379 |
Redis connection |
WHATSAPP_BRIDGE_URL |
http://localhost:8765 |
Bridge HTTP URL |
WHATSAPP_BRIDGE_ENABLED |
true |
Enable/disable WhatsApp tool |
BRIDGE_PORT |
8765 |
Bridge listen port (Go side) |
10. File Reference¶
ai-parrot/
├── docker-compose.yml # ← Redis + Bridge + App
├── Makefile # ← build/run targets
├── services/whatsapp-bridge/
│ ├── main.go # ← Bridge Go source
│ ├── Dockerfile # ← Multi-stage build
│ ├── go.mod / go.sum # ← Go dependencies
├── parrot/autonomous/
│ ├── orchestrator.py # ← AutonomousOrchestrator
│ └── hooks/
│ ├── base.py # ← BaseHook ABC
│ ├── models.py # ← HookEvent, configs, factories
│ ├── manager.py # ← HookManager
│ ├── whatsapp_redis.py # ← WhatsAppRedisHook
│ └── __init__.py # ← Public exports
└── examples/
└── whatsapp_orchestrator.py # ← Working example
Troubleshooting¶
| Problem | Solution |
|---|---|
| QR code expired | Restart Bridge: docker compose restart whatsapp-bridge |
"No QR code available" on /qr |
Bridge may already be authenticated — check /health |
| Messages not arriving | Verify Redis subscription: redis-cli SUBSCRIBE whatsapp:messages |
AttributeError: auto_reply |
Update WhatsAppRedisHookConfig in models.py (see this guide) |
| Bridge unhealthy | Check Go logs: docker compose logs whatsapp-bridge |
| Auth lost after restart | Session stored in data/whatsapp/whatsapp.db — ensure volume is mounted |