Free lesson · GenAI Solutions Architecture
Optimize A2A network topology for latency and reliability
You will build an A2ATopologyOptimizer that analyzes the agent communication graph and recommends network reorganizations to minimize delegation latency and maximize reliability. Define a NetworkTopology Pydantic model with fields topology_id: str, agents: list[AgentNode], connections: list[AgentConnection], measured_at: datetime, optimization_score: float, and total_delegations_24h: int. The AgentNode model includes agent_id: str, region: str, capabilities: list[str], current_load: float, avg_response_ms: float, trust_score: float, and connection_count: int. The AgentConnection model captures source_agent: str, target_agent: str, avg_latency_ms: float, delegation_count: int, success_rate: float, bandwidth_utilization: float, last_delegation: datetime, and connection_type: str. Implement build_topology_graph() -> NetworkTopology that queries the a2a_delegations table to reconstruct the actual communication graph, computing edge weights from delegation latency, frequency, and success rate over a configurable time window. Build AgentProximityMapper with method find_optimal_path(source: str, target_skill: str) -> DelegationPath that uses Dijkstra's algorithm on the topology graph with edge weights computed as latency_ms * (1 / success_rate) to find the lowest-cost delegation path, considering multi-hop scenarios where Agent A delegates to Agent B who sub-delegates to Agent C. Return DelegationPath with path: list[str], estimated_latency_ms: float, hop_count: int, bottleneck_agent: str, and alternative_paths: list[DelegationPath]. Implement ConnectionPoolManager that maintains persistent HTTP/2 connections between frequently communicating agent pairs, stored in Redis as a2a:pool:{source}:{target} with connection count, last-used timestamp, and health status. Build TopologyRecommendationEngine with method suggest_optimizations() -> list[TopologyRecommendation] that identifies: (1) high-latency connections exceeding p95 baseline by 2x that would benefit from co-location, (2) single-point-of-failure agents with in-degree > 5 and no replicas that need replication, (3) underutilized connections with fewer than 10 delegations per day that can be pruned to reduce connection overhead, and (4) missing direct connections between agents that communicate via 3+ hops more than 100 times per day. Each TopologyRecommendation contains recommendation_type, affected_agents, expected_improvement, effort_level, and priority. Store recommendations in PostgreSQL a2a_topology_recommendations table with columns recommendation_id, type, agents_json, expected_improvement_pct, status, created_at. Emit Prometheus metrics a2a_topology_latency_seconds{source,target}, a2a_connection_pool_size{source,target}, a2a_topology_optimization_score, a2a_delegation_hops_total{path_length}, and a2a_topology_recommendations_total{type,status}. Build Grafana visualization of the agent topology graph with edge thickness proportional to traffic volume and color indicating health (green for success_rate > 0.99, yellow > 0.95, red below).
Course: GenAI Architecture & Design Patterns · Chapter 12 · A2A Agent Network
Free to read — no subscription required.
Introduction
When an A2A agent network grows beyond a handful of nodes, the communication topology becomes the single largest determinant of end-to-end task completion latency. A naively connected mesh of 20 agents produces 190 potential edges—each carrying discovery overhead, authentication handshakes, and streaming artifact channels. Without deliberate topology optimization, delegation chains balloon into multi-hop paths that accumulate latency at every intermediary, while single points of failure silently emerge at high-centrality nodes. This section equips you to analyze an existing A2A communication graph and compute concrete optimization recommendations that cut delegation latency and remove the single points of failure hiding at high-centrality nodes. By the end, you will be able to construct a weighted topology graph from observed delegation metrics, generate prioritized optimization recommendations, and validate that each change lowers worst-case latency without introducing a new bottleneck.
Key Terminology
- Betweenness centrality: A graph metric measuring how often a node appears on shortest paths between other nodes; high values indicate bottleneck risk in delegation chains.
- Edge weight: A composite score assigned to each agent-to-agent communication link, typically combining observed latency, failure rate, and throughput capacity.
- Hub node: An agent with disproportionately high degree (number of connections) that routes a large fraction of all delegations—a candidate for replication or load balancing.
- Node replication: Deploying a second instance of a high-degree agent behind a load balancer so its combined in- and out-degree—and thus its failure blast radius—splits across replicas.
- Network diameter: The longest shortest path between any two agents in the topology; directly correlates with worst-case delegation chain latency.
Concepts
Practical Optimization Workflow
Applying topology optimization in production follows a disciplined cycle:
-
Collect: Instrument every A2A delegation with OpenTelemetry spans capturing latency, status codes, and artifact sizes. Export spans to a time-series database (Prometheus, ClickHouse) with agent-pair cardinality.
-
Build the graph: Periodically (every 5–15 minutes), query aggregated metrics and construct a NetworkTopology instance. Edge metrics should use p95 latency (not mean) and rolling 1-hour failure rates to avoid reacting to transient spikes.
-
Analyze and recommend: Run recommend_optimizations against the current topology snapshot. Filter recommendations through a stability gate—only surface actions that persist across three consecutive analysis windows to prevent oscillation.
-
Apply incrementally: Execute one optimization action per cycle. After replicating a node or adding a retry policy, wait for the next collection window to measure impact before applying the next recommendation. This prevents cascading changes that are impossible to attribute.
-
Validate topology invariants: After every change, confirm the modification achieved its goal without regression—no node newly exceeds the degree threshold, network diameter did not grow, and p95 latency on the critical delegation paths improved or held steady.
Do's
- ✓
Do use p95 latency for edge weights - Mean latency hides tail behavior that causes task timeouts. The p95 captures the experience of the delegation chains that matter most—the ones pushing against your SLA.
- ✓
Do version your topology snapshots - Store each NetworkTopology instance with a timestamp. When a regression occurs, diff the current snapshot against the last known-good topology to identify which edge or node change caused the degradation.
- ✓
Do weight
hop_countinto the composite score - A single-hop link at 50ms and a three-hop path at 50ms per hop are not equivalent; multiplying latency byhop_countincompute_edge_weightmakes Dijkstra prefer shorter delegation chains, which directly lowers worst-case completion latency.
Don'ts
- ✗
Don't optimize for a single metric - Minimizing latency alone may route all traffic through a single fast path, creating a bottleneck. The composite weight function exists to balance latency, reliability, and throughput simultaneously.
- ✗
Don't replicate a node before confirming it is the bottleneck - Adding a replica to an agent that isn't actually saturated spends infrastructure without lowering latency; run
find_bottlenecksand confirm the node's degree exceeds the threshold before recommending replication. - ✗
Don't react to single-window anomalies - A momentary latency spike on one edge should not trigger node replication. The three-window stability gate prevents expensive infrastructure changes driven by transient conditions.
Code Walkthrough
Having established the metrics and structural patterns that define a healthy topology, you will now build the analyzer that measures them and turns those measurements into concrete recommendations.
Understanding A2A Topology Primitives
Before optimizing, you must classify the topology you are working with. A2A networks typically evolve through three structural patterns:
- Star topology: A single orchestrator delegates to all specialist agents. Simple to reason about, but the orchestrator becomes a bottleneck and single point of failure. Latency is bounded by the slowest specialist per fan-out round.
- Hierarchical topology: Orchestrators delegate to sub-orchestrators, which in turn delegate to leaf agents. This reduces fan-out at any single node but introduces multi-hop latency for deep hierarchies.
- Mesh topology: Agents discover and delegate to each other directly via agent card registries. Offers the lowest theoretical latency (single-hop) but requires every agent to maintain discovery caches and handle authentication with potentially every other agent.
The optimal topology for a given workload sits somewhere between these extremes. The A2ATopologyOptimizer you will build in the lab analyzes the actual communication graph—edge weights derived from observed delegation latency, failure rates, and throughput—and recommends specific structural changes.
A2A protocol networks adopt three distinct topologies that shape how agents discover and delegate tasks. The Star pattern routes all communication through a single Orchestrator node (O1) that dispatches to Agent A through Agent D—simple but a single point of failure. The Hierarchical pattern introduces Sub-Orch 1 and Sub-Orch 2 beneath a Root Orchestrator (O2), cutting fan-out at any single node by scoping agent groups into subtrees. The Mesh pattern connects every agent bidirectionally (A3 <--> B3, C3 <--> D3, etc.), maximizing resilience through redundant streaming paths at the cost of n(n-1)/2* connections to manage.
Code snippet mermaid
Loading diagram...
- Line 1: Declares a Mermaid diagram with a top-down (TD) directed graph layout.
- Lines 2-7: Defines a "Star" subgraph where a central Orchestrator node (O1) has directed edges to four leaf agents (A1 through D1), illustrating a hub-and-spoke topology where one coordinator delegates to all workers.
- Lines 9-16: Defines a "Hierarchical" subgraph where a Root Orchestrator (O2) delegates to two intermediate
Sub-Orchnodes (S1, S2), each of which in turn connects to two agents, forming a tree-shaped chain of command with two levels of delegation. - Lines 18-25: Defines a "Mesh" subgraph where four agents (A3 through D3) are connected with bidirectional edges (
<-->), creating a fully connected peer-to-peer network where every agent can communicate directly with every other agent without a central coordinator.
Each topology above carries distinct trade-offs. Star topologies are trivially debuggable—every task flows through one node—but that node's failure halts the entire network. Hierarchical topologies distribute load but increase hop count. Mesh topologies minimize hops but explode the connection count—every pair of agents maintains its own discovery cache and delegation channel, so an n-agent mesh carries n*(n-1)/2 links to monitor and keep healthy.
Building the Topology Analyzer
The foundation of topology optimization is an accurate graph representation of your A2A network. Each agent becomes a node, each observed delegation becomes a directed edge, and edge attributes capture the performance characteristics you want to optimize. The following implementation defines the NetworkTopology dataclass and the core A2ATopologyOptimizer class. The NetworkTopology stores nodes and weighted edges, while the A2ATopologyOptimizer provides methods including analyze_graph to compute centrality metrics, find_bottlenecks to identify hub nodes exceeding a degree threshold, and compute_edge_weights to derive composite scores from raw latency and failure observations.
Code snippet python
1from dataclasses import dataclass, field 2import heapq 3from collections import defaultdict 4 5@dataclass 6class EdgeMetrics: 7 latency_ms: float 8 failure_rate: float # 0.0 to 1.0 9 throughput_rps: float 10 hop_count: int = 1 11 12@dataclass 13class NetworkTopology: 14 nodes: list[str] = field(default_factory=list) 15 edges: dict[tuple[str, str], EdgeMetrics] = field(default_factory=dict) 16 adjacency: dict[str, list[str]] = field(default_factory=lambda: defaultdict(list)) 17 18 def add_edge(self, source: str, target: str, metrics: EdgeMetrics) -> None: 19 if source not in self.nodes: 20 self.nodes.append(source) 21 if target not in self.nodes: 22 self.nodes.append(target) 23 self.edges[(source, target)] = metrics 24 self.adjacency[source].append(target) 25 26class A2ATopologyOptimizer: 27 def __init__(self, topology: NetworkTopology): 28 self.topology = topology 29 30 def compute_edge_weight(self, metrics: EdgeMetrics) -> float: 31 """Composite weight: lower is better. Penalizes latency and failures.""" 32 reliability = 1.0 - metrics.failure_rate 33 if reliability <= 0.0: 34 return float("inf") 35 return (metrics.latency_ms * metrics.hop_count) / (reliability * metrics.throughput_rps) 36 37 def find_bottlenecks(self, degree_threshold: int = 5) -> list[dict]: 38 """Identify nodes whose in-degree + out-degree exceeds threshold.""" 39 degree_map: dict[str, int] = defaultdict(int) 40 for source, target in self.topology.edges: 41 degree_map[source] += 1 42 degree_map[target] += 1 43 return [ 44 {"agent": node, "degree": deg, "is_bottleneck": True} 45 for node, deg in degree_map.items() 46 if deg >= degree_threshold 47 ] 48 49 def shortest_delegation_path(self, start: str, end: str) -> tuple[list[str], float]: 50 """Dijkstra over composite edge weights for optimal delegation route.""" 51 weights = { 52 edge: self.compute_edge_weight(m) 53 for edge, m in self.topology.edges.items() 54 } 55 dist = {node: float("inf") for node in self.topology.nodes} 56 prev: dict[str, str | None] = {node: None for node in self.topology.nodes} 57 dist[start] = 0.0 58 queue = [(0.0, start)] 59 while queue: 60 d, u = heapq.heappop(queue) 61 if u == end: 62 break 63 if d > dist[u]: 64 continue 65 for v in self.topology.adjacency[u]: 66 w = weights.get((u, v), float("inf")) 67 if dist[u] + w < dist[v]: 68 dist[v] = dist[u] + w 69 prev[v] = u 70 heapq.heappush(queue, (dist[v], v)) 71 path, current = [], end 72 while current is not None: 73 path.append(current) 74 current = prev[current] 75 path.reverse() 76 return (path, dist[end]) if path[0] == start else ([], float("inf"))
- Lines 1-3: Import dataclass for structured data, heapq for the priority queue used in Dijkstra's algorithm, and defaultdict for adjacency list construction.
- Lines 5-9: Define EdgeMetrics capturing the four raw measurements per communication link: latency in milliseconds, failure rate as a float between 0.0 and 1.0, throughput in requests per second, and hop count defaulting to 1.
- Lines 11-20: Define NetworkTopology with a node list, an edge dictionary keyed by source-target tuples, and an adjacency list built via defaultdict. The add_edge method ensures both nodes are registered before recording the edge.
- Lines 22-24: The A2ATopologyOptimizer constructor accepts a NetworkTopology instance and stores it for all subsequent analysis operations.
- Lines 26-30: compute_edge_weight produces a composite score where high latency and high failure rates increase the weight (worse), while high throughput decreases it. An edge with a failure rate of 1.0 returns
float("inf"), effectively removing it from consideration. - Lines 32-39: find_bottlenecks iterates all edges to compute total degree per node, then returns a list of dictionaries for every node meeting or exceeding the degree_threshold—these are candidates for replication or decomposition.
- Lines 41-60: shortest_delegation_path implements Dijkstra's algorithm over the composite weights. It reconstructs the path via the prev dictionary and returns both the ordered node list and total cost. If no path exists, it returns an empty list and
float("inf").
Generating Optimization Recommendations
Raw metrics and bottleneck detection are necessary but not sufficient. The optimizer must translate analysis into actionable recommendations: which edges to add, which nodes to replicate, and where to insert caching layers. The following recommend_optimizations method on the A2ATopologyOptimizer class examines bottleneck nodes, high-latency edges exceeding a configurable latency_threshold_ms, and unreliable edges surpassing a failure_threshold. It produces a list of OptimizationAction dataclass instances, each specifying the action type, target agent or edge, and a human-readable rationale that operations teams can review before applying changes.
Code snippet python
1from enum import Enum 2 3class ActionType(Enum): 4 REPLICATE_NODE = "replicate_node" 5 ADD_DIRECT_EDGE = "add_direct_edge" 6 INSERT_CACHE = "insert_cache" 7 ADD_RETRY_POLICY = "add_retry_policy" 8 SPLIT_HUB = "split_hub" 9 10@dataclass 11class OptimizationAction: 12 action: ActionType 13 target: str 14 rationale: str 15 priority: int # 1 = highest 16 17def recommend_optimizations( 18 optimizer: A2ATopologyOptimizer, 19 latency_threshold_ms: float = 200.0, 20 failure_threshold: float = 0.05, 21 degree_threshold: int = 5, 22) -> list[OptimizationAction]: 23 actions: list[OptimizationAction] = [] 24 bottlenecks = optimizer.find_bottlenecks(degree_threshold) 25 for bn in bottlenecks: 26 actions.append(OptimizationAction( 27 action=ActionType.REPLICATE_NODE, 28 target=bn["agent"], 29 rationale=f"Degree {bn['degree']} exceeds threshold {degree_threshold}", 30 priority=1, 31 )) 32 for (src, tgt), metrics in optimizer.topology.edges.items(): 33 if metrics.latency_ms > latency_threshold_ms: 34 actions.append(OptimizationAction( 35 action=ActionType.INSERT_CACHE, 36 target=f"{src}->{tgt}", 37 rationale=f"Latency {metrics.latency_ms}ms exceeds {latency_threshold_ms}ms", 38 priority=2, 39 )) 40 if metrics.failure_rate > failure_threshold: 41 actions.append(OptimizationAction( 42 action=ActionType.ADD_RETRY_POLICY, 43 target=f"{src}->{tgt}", 44 rationale=f"Failure rate {metrics.failure_rate:.1%} exceeds {failure_threshold:.1%}", 45 priority=1, 46 )) 47 actions.sort(key=lambda a: a.priority) 48 return actions
- Lines 1-8: Define ActionType as an Enum with five optimization strategies: replicating overloaded nodes, adding direct edges to bypass multi-hop paths, inserting artifact caches, adding retry policies to unreliable edges, and splitting an overloaded hub into specialized sub-agents.
- Lines 10-15: The OptimizationAction dataclass pairs an action type with its target (agent name or edge identifier), a human-readable rationale, and a numeric priority where 1 indicates the highest urgency.
- Lines 17-22: The recommend_optimizations function accepts the optimizer instance and three tunable thresholds. These defaults—200ms latency, 5% failure rate, degree of 5—reflect production baselines for mid-scale agent networks, but callers should adjust based on their SLA requirements.
- Lines 24-31: For each bottleneck node returned by find_bottlenecks, the function emits a REPLICATE_NODE action at priority 1. Replication means deploying a second instance of the agent behind a load balancer, halving the degree at each replica.
- Lines 32-44: The edge iteration loop checks two independent conditions. Edges exceeding the latency threshold receive an INSERT_CACHE recommendation—artifact caching at the source reduces repeated round-trips for idempotent queries. Edges exceeding the failure threshold receive an ADD_RETRY_POLICY recommendation at priority 1, since unreliable links directly threaten task completion SLAs.
- Lines 45-46: The final sort by priority ensures operations teams address the most critical issues first when processing the recommendation list.
You'll know your analyzer works when, given a NetworkTopology built from a recent window of delegations, recommend_optimizations flags every node whose degree exceeds your threshold as REPLICATE_NODE, raises an INSERT_CACHE or ADD_RETRY_POLICY for every edge past its latency or failure threshold, and returns the actions sorted by ascending priority — at which point it is ready to drive one-action-per-cycle optimization.
Do's and Don'ts
Now that you have built and validated the analyzer, these practices keep its recommendations trustworthy once the topology is live in production.
Do's
- ✓Do populate all four
EdgeMetricsfields —latency_ms,failure_rate,throughput_rps, andhop_count— for every observed delegation before callingcompute_edge_weight— the formula divideslatency_ms × hop_countby(1 − failure_rate) × throughput_rps, so a missing or zeroed field silently drives the composite score to infinity or to an artificially low value, producing path rankings that misroute real traffic. - ✓Do run
find_bottleneckswith adegree_thresholdcalibrated to your actual fan-out capacity before hardening a Star or Hierarchical topology — the Star pattern routes every delegation through one orchestrator node, and once that node's combined in-degree plus out-degree exceeds the threshold,find_bottleneckssurfaces it as a structural bottleneck whose failure halts the entire network; catching this analytically is cheaper than discovering it under load. - ✓Do choose among Star, Hierarchical, and Mesh as an explicit architectural decision driven by your latency budget and failure tolerance — Star topologies bound latency to one hop but concentrate failure risk at the orchestrator; Hierarchical topologies cut fan-out per node at the cost of extra hops; Mesh topologies minimize delegation hops but grow the connection count to n*(n−1)/2 edges, each an independent link you must keep healthy.
Don'ts
- ✗Don't rank A2A paths on raw
latency_msalone — a fast link with a highfailure_rateproduces a reliability factor near zero incompute_edge_weight, driving its composite score toward infinity and correctly deprioritizing it; ignoring failure rate routes traffic toward links that fail frequently and appear fast only in the successful-sample average. - ✗Don't adopt a Mesh topology without accounting for the quadratic growth in links to monitor — 20 agents produce 190 bidirectional edges, each an independent delegation channel with its own health, latency, and retry behavior; without instrumenting every one, individual link failures go undetected and cascade instead of being routed around through redundant paths, defeating the resilience the Mesh was chosen to provide.
- ✗Don't set
degree_thresholdinfind_bottleneckshigher than the throughput your orchestrator or sub-orchestrator node was measured to sustain — an overly generous threshold masks high-centrality nodes in Hierarchical topologies that are already saturated, allowing a sub-orchestrator whosethroughput_rpsis near its ceiling to accumulate edges silently until one failure partitions the entire agent group beneath it.
This lesson is free to read. Its 3 hands-on labs — real code, in a cloud IDE — are part of the GenAI Solutions Architecture subscription.
From · cancel anytime
More free lessons in GenAI Architecture & Design Patterns
- Ch 12Build A2A agent card registry with capability advertisement
- Ch 12Implement A2A task delegation with streaming artifact exchange
- Ch 12Validate A2A communication reliability with failure injection
- Ch 12Build A2A agent trust and authorization framework
- Ch 12Optimize A2A network topology for latency and reliabilityYou are here
- Ch 12Create A2A network operations dashboard with federation view
- Ch 13Implement event backbone with Redis Streams for AI workloads