Why architecture matters here
The reason backpressure is architectural rather than a tuning knob is that its absence has no gentle failure mode. A system without flow control does not degrade linearly as load rises; it works fine right up to a saturation point and then falls off a cliff. Below capacity, queues stay shallow and latency is flat. Above capacity, every excess task lengthens the queue, every queued task ages while it waits, and latency climbs without bound while throughput flatlines or drops — the classic congestion collapse. An orchestrator that keeps firing tasks into a saturated specialist is not being aggressive; it is actively manufacturing the collapse, because each new task consumes memory for its envelope, its context, and its place in line long before it consumes any compute.
Across the A2A boundary the stakes compound because the failure is shared. A remote agent is usually multi-tenant: one orchestrator's refusal to slow down starves every other caller. And LLM-backed agents have unusually expensive, unusually variable unit work — a single task might take fifty milliseconds or fifty seconds depending on tool calls and token counts — so the mismatch between arrival rate and service rate is both large and unpredictable. This is precisely the regime where you cannot size a fixed queue and hope; you need the consumer's real-time pace to reach back and throttle the producer.
The design choice, then, is not whether to bound the system but where the boundedness lives and how the limit is communicated. Put the bound only at the consumer and it protects itself by dropping work the producer already paid to send. Put an honest signal on the wire — a 429, a credit count, a stalled stream — and the producer stops generating waste at the source. A well-designed A2A system pushes the bound as far upstream as it can, so the cheapest possible thing (not starting a task) happens instead of the most expensive (accepting, queuing, and then abandoning it).
The architecture: every piece explained
Top row: the request path. A client agent produces tasks — via tasks/send, a streaming subscription, or a batch of delegations. The transport (HTTP with SSE, or gRPC) carries them but, crucially, also carries the return signals: status codes, response headers, and stream cadence. At the consumer's edge sits a bounded queue — the admission window — that decides, per arriving task, whether there is room. This is the single most important structural element: the queue is bounded, and its bound is a deliberate capacity decision, not a memory accident. The remote agent pulls from that queue at whatever rate its models and tools allow.
Middle row: the signals that carry pressure back. Credit signals implement flow control the way TCP and gRPC do — the consumer advertises a window ('I can take 20 more tasks'), the producer spends credits as it sends and stops when they run out, and the consumer replenishes credits as it drains. 429 with Retry-After is the explicit, stateless version: when the admission window is full, the consumer rejects with a status the client is contractually obliged to honor, backing off for the advised interval. SSE pull cadence is the subtlest: on a streaming response, an HTTP reader that stops reading causes TCP's own receive window to close, which stops the writer — the consumer's reading speed literally governs the producer's writing speed without any application-level message. Task queue depth is the gauge behind all of them: the number the consumer watches to decide when to start pushing back.
Bottom rows: what happens when signals are not enough. Shedding is deliberate load reduction — reject low-priority tasks, defer deferrable ones, or degrade quality (cheaper model, skipped enrichment) to fit the work into capacity. Buffering with spill absorbs short bursts, but the buffer is always bounded; overflow spills to durable storage or is rejected, never allowed to grow without limit into an OOM. The ops strip is the discipline that keeps it honest: queue-depth SLOs, saturation alerts fired on sustained high-water rather than instantaneous spikes, drop accounting so shed load is counted and not silently lost, and load tests that deliberately drive the system past capacity to confirm it bends instead of breaking.
End-to-end flow
Trace a burst through a healthy A2A system. An orchestrator agent receives a spike — a customer uploads a 900-row spreadsheet, and the orchestrator wants to delegate one enrichment task per row to a remote research agent whose steady-state capacity is about 40 concurrent tasks. Instead of firing 900 tasks/send calls in a tight loop, the client respects the credit window the research agent advertised on connection: 60 in flight. It sends 60, then waits.
The research agent's admission queue fills to its bound of 60 immediately; its worker pool of 40 pulls tasks and starts them, each taking two to eight seconds. As each task completes, a credit is returned to the client in the response metadata, and the client releases exactly one more task into the pipe. The system self-clocks: the client's send rate converges on the agent's completion rate, queue depth hovers near capacity without exceeding it, and the 900 tasks drain over a couple of minutes at the agent's true throughput. No task was dropped; none piled up in memory it could not use.
Now the stress path. A second orchestrator joins and floods the same agent, ignoring credits (a misbehaving or legacy client). The admission window is already full, so the agent responds to the extra tasks with 429 Too Many Requests and a Retry-After: 3. The compliant first orchestrator, still inside its credit budget, is unaffected — its tasks keep flowing — while the flooder is forced to back off. This is the payoff of pushing the bound to an explicit signal: fairness and isolation fall out of it. If the flooder also ignores the 429 and keeps hammering, the agent's last line of defense is shedding: it classifies the flooder's tasks as low-priority (no credits reserved) and rejects them outright, protecting the credited traffic and its own liveness.
Finally, a streaming case. A long-running task streams incremental results back over SSE to a client that writes each chunk to a slow downstream sink. The client reads slowly; TCP's receive window closes; the agent's SSE writer blocks on the socket and naturally stops generating ahead of the reader. No message said 'slow down' — the transport's own flow control carried the pressure end to end, and the agent's memory stayed flat because it never buffered more than one window of unread output. When the whole system is observed, the queue-depth graph tells the story: a plateau at capacity during the burst, a clean drain afterward, and a spike of counted 429s isolated to the misbehaving caller — exactly the shape a load test predicted.
It is worth naming what did not happen, because the non-events are the point. Memory never climbed, because no component ever accepted more work than it could hold. Latency for the compliant caller never spiked, because its credited tasks were never queued behind the flood. And no task was silently lost, because every rejection was an explicit, counted 429 that the orchestrator could reconcile against its own list of 900 rows — re-submitting the shed ones after the advised delay. The system stayed inside a stable operating envelope not by luck or over-provisioning but because the consumer's pace was made visible to every producer and every producer was built to obey it. That is the entire contract of backpressure, and everything else in this architecture is machinery for keeping it.