A2A · ROUTING VERSUS TASK STATE
A2A load balancing: keep a local task on its owning server
Send eight A2A requests from two clients through a load balancer and see whether each follow-up lands where its task state lives.
Your experiment
Client A creates T1, polls it, continues it, creates T3, continues that and finally polls T1 again; client B creates T2 and polls it. Start with round robin and in-memory task state, then try affinity, owner-routed task IDs, a shared store and a replica crash.
Play replays the computed run over sixty inspection seconds; Next event steps, Restart rewinds, Speed changes the pace and Inspection time scrubs. Any input change recomputes the run and rewinds it without autoplay.
Event trace
Computed data
Final metrics
Teaching model of where A2A task state lives, not an A2A SDK. Methods are labels from the A2A JSON-RPC surface; TaskNotFoundError (-32001) is the error a server returns for a task it does not know. Round robin skips crashed replicas; affinity re-pins a client whose replica is gone. Real deployments add health checks, connection reuse, streaming and push notifications, which are omitted.
Requests follow the balancer, state stays put
Three A2A server replicas sit behind one endpoint. Client A sends a new message that creates T1, polls it with tasks/get, continues it, creates T3, continues that and finally polls T1 again; client B creates T2 and polls it. With round robin and task state kept in the memory of the creating replica, T1 is created on r1 and T2 on r2, but the poll for T1 lands on r3 and gets TaskNotFoundError (-32001). Four of the eight requests miss their task: TASK STATE MISSED, 4/8 served. The balancer spreads requests evenly, which is exactly what scatters a multi-turn task.
Affinity and owner-routed IDs
Set Load-balancer routing to Sticky by client session: every request from A reaches r1 and every request from B reaches r2, so all eight are served. Owner replica encoded in the task ID gets the same result a different way: the balancer reads the owner from the task ID on every follow-up, so it works even when one client talks to several tasks or reconnects through another connection. Server replicas changes where round robin sends each request; with 2 replicas two requests miss, with 3 or 4 replicas four miss.
A crash takes local state with it
Check Replica r1 crashes before request 5. With affinity, client A is re-pinned to r2, which creates and continues T3 correctly, but the final poll for T1 fails because T1 lived only in r1 memory: 7/8 served. With owner-routed IDs the poll for T1 has nowhere to go and reports the owner as down. Routing can only find state that still exists; neither strategy protects a task whose only copy disappeared.
Shared state removes the coupling
Set Where task state lives to Shared task store. Every replica reads and writes the same store, so round robin, affinity and owner routing all serve 8 of 8, with and without the crash; the trace marks each request that read a task from the store on a replica that did not create it. The cost moves to the store: it is now on every request path, needs its own availability and must be the single source of truth for task status and history.
Reading the run and its limits
Play, Next event, Restart, Speed and Inspection time step through the setup, the crash and each request across sixty seconds; the table lists the client, method, task, replica and outcome. The model routes eight fixed requests and omits health checks, retries by the client, streaming connections, push notifications and store latency. It shows why the routing key and the location of task state must be chosen together.