Lesson 3 of 6·11 min read

Agent Communication & State

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.

Message Passing Between Sub-Workflows

Direct Message Passing

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" }
    }
  }
}

Message Queue Pattern

For more complex systems, use a message queue between agents:

Producer Agent → Redis Queue → Consumer Agent
                    ↓
              Dead Letter Queue (on errors)
PatternAdvantagesDisadvantages
DirectSimple, synchronous, immediate resultsNo decoupling, blocking
QueueDecoupled, scalable, retry-capableMore complex, eventual consistency
Pub/SubMultiple consumers, event-drivenOrder not guaranteed

Shared Databases for Persistent State

For state that must persist beyond individual executions, use a database:

Schema Design for Multi-Agent State

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)
);

State Access in n8n

-- 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';

Redis for Real-Time State

For fast, ephemeral state between agents, Redis is ideal:

Use CaseRedis StructureTTL
Agent statusHash: pipeline:{id}:status1 hour
Intermediate resultsString: pipeline:{id}:agent:{name}:result30 min
LocksString: pipeline:{id}:lock:{resource}60 sec
CountersIncr: pipeline:{id}:iteration1 hour

Distributed Locking

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

Conflict Resolution

What happens when two agents deliver contradictory results?

Strategies

  1. Last Writer Wins: Simple but risky — the last agent overwrites
  2. Voting: Multiple agents evaluate, majority wins
  3. Orchestrator Decision: The orchestrator chooses based on confidence scores
  4. Human-in-the-Loop: At low confidence, a human is involved

Voting Example in n8n

{
  "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.