Architecture Deep Dive

A2A Multi-Agent System

How I designed a production multi-agent architecture using Agent-to-Agent (A2A) protocol, LangGraph, Redis Streams, and SSE — with supervisor orchestration, parallel subagents, synthesis, and fault tolerance.

A2A Protocol LangGraph Redis Streams SSE Agent Cards Kubernetes Python
System Architecture
Full A2A Orchestration Flow
Each subagent runs as an independent service with its own pod and scaling policy. Communication between services happens exclusively via Redis Streams — SSE is reserved for the last mile to the browser.
UI Client SSE connection Chat Service tracks SSE · subscribes Redis Stream SSE + convo_id Supervisor (LangGraph) loads agent cards · compiles graph request + thread_id Agent Cards /.well-known/agent.json Agent A retrieval · own pod Agent B analysis · own pod Agent C context · own pod Send() — parallel dispatch Redis Streams stream:{convo_id} · sliding TTL · consumer groups started → working → done/failed supervisor consumer group ui consumer group → SSE all_done → Chat Service → SSE closes
Core Protocol
Three Things A2A Defines
Agent Card
Every agent exposes /.well-known/agent.json — name, capabilities, skills, input/output modes, endpoint. Supervisor fetches this at startup. No hardcoding.
Message Contract
Standard envelope for every agent communication — text, files, structured data. Same format regardless of which framework the agent is built on. True interoperability.
Task Lifecycle
Every task has standard states: submitted → working → completed / failed / cancelled. No ambiguity across teams or frameworks.
Orchestration
Step-by-Step Flow
01
Supervisor loads Agent Cards at startup — fetches /.well-known/agent.json from each agent service and compiles the LangGraph graph dynamically.
02
Request arrives — supervisor dispatches independent agents in parallel via LangGraph's Send() API. Each gets its own input slice of state simultaneously.
03
Agents publish lifecycle events to Redis Stream keyed by stream:{convo_id} — started, working (tokens), done, or failed.
04
Supervisor actively listens via its own Redis consumer group — detects when all agents complete, then triggers the Synthesis agent as a DAG join node.
05
Synthesis agent aggregates all results, streams the final synthesised response token by token to Redis, then publishes all_done.
06
Chat Service reads every event via its own consumer group and forwards to the browser's SSE connection. all_done closes the connection cleanly.
Reliability
Fault Tolerance Patterns
Agent fails
Structured error event with retryable flag published to stream. Supervisor self-corrects via LLM or skips.
Agent hangs
Per-agent deadline in supervisor listener. Timeout triggers retry with backoff or graceful skip with partial result.
Pod crashes
Heartbeat events in Redis Stream. Supervisor detects staleness and handles — retry or fallback.
Optional agent down
Proceed with results from available agents. Don't fail the whole request for a non-critical agent.
User disconnects
Redis Stream persists with sliding TTL. Reconnect with last_event_id replays missed events. TTL expired — serve from DB on login.
Decision Guide
Single Agent vs Multi-Agent
Stay Single Agent Go Multi-Agent
Sequential tasks, no parallelism opportunity Tasks are genuinely independent and parallelisable
Context flows freely between every step Different agents have different load profiles
Small team — more services = more ops burden Different teams need to own different capabilities
Still exploring the problem space One context window genuinely isn't enough
Sub-100ms latency budget — network hops hurt Need to swap components without touching the rest

Let's talk architecture

Building agentic systems or want to discuss this design? Reach out.

Get in touch LinkedIn