When multiple agents collaborate, communication becomes the biggest challenge. How do agents pass data? Where is shared state stored? How are conflicts resolved? This chapter covers the proven patterns for n8n multi-agent systems.
The simplest approach: The orchestrator passes data as parameters to sub-workflows.
{
"execute_workflow": {
"workflowId": "researcher-agent",
"input": {
"task": "Research current trends in Edge AI",
"context": { "previous_findings": [], "iteration": 1 },
"config": { "max_sources": 5, "language": "en" }
}
}
}
For more complex systems, use a message queue between agents:
Producer Agent → Redis Queue → Consumer Agent
↓
Dead Letter Queue (on errors)
| Pattern | Advantages | Disadvantages |
|---|---|---|
| Direct | Simple, synchronous, immediate results | No decoupling, blocking |
| Queue | Decoupled, scalable, retry-capable | More complex, eventual consistency |
| Pub/Sub | Multiple consumers, event-driven | Order not guaranteed |
For state that must persist beyond individual executions, use a database:
CREATE TABLE agent_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pipeline_id UUID NOT NULL,
agent_name TEXT NOT NULL,
status TEXT DEFAULT 'pending', -- pending, running, completed, failed
input_data JSONB,
output_data JSONB,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
error_message TEXT
);
CREATE TABLE agent_state (
pipeline_id UUID NOT NULL,
key TEXT NOT NULL,
value JSONB NOT NULL,
updated_by TEXT NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (pipeline_id, key)
);
-- Researcher writes results
INSERT INTO agent_state (pipeline_id, key, value, updated_by)
VALUES ($1, 'research_findings', $2::jsonb, 'researcher')
ON CONFLICT (pipeline_id, key) DO UPDATE SET value = $2::jsonb, updated_by = 'researcher';
-- Writer reads results
SELECT value FROM agent_state
WHERE pipeline_id = $1 AND key = 'research_findings';
For fast, ephemeral state between agents, Redis is ideal:
| Use Case | Redis Structure | TTL |
|---|---|---|
| Agent status | Hash: pipeline:{id}:status | 1 hour |
| Intermediate results | String: pipeline:{id}:agent:{name}:result | 30 min |
| Locks | String: pipeline:{id}:lock:{resource} | 60 sec |
| Counters | Incr: pipeline:{id}:iteration | 1 hour |
When multiple agents access the same resource, avoid race conditions with Redis locks:
SET pipeline:abc:lock:database LOCKED NX EX 60
-- NX = only set if not exists
-- EX 60 = auto-release after 60 seconds
What happens when two agents deliver contradictory results?
{
"function": "const results = items.map(i => i.json);\nconst approved = results.filter(r => r.decision === 'APPROVE').length;\nconst total = results.length;\nreturn [{ json: { decision: approved > total/2 ? 'APPROVE' : 'REVISE', votes: { approved, total } } }];"
}
Practical tip: Start with direct message passing and PostgreSQL for state. Only add Redis when you need real-time coordination (e.g., parallel agents reading/writing the same state). For 90% of use cases, the simple variant is sufficient.