Why architecture matters here
The architecture matters because the alternative to real cancellation is not a minor inefficiency — it is unbounded waste plus silent inconsistency. Consider an agent with no cancel path. A client delegates a research task with a five-minute budget; the user abandons it after ten seconds. The remote agent has no way to learn this, so it runs the full five minutes, makes forty tool calls, burns tens of thousands of tokens, and produces a report that is discarded on arrival. Multiply by every abandoned request in a busy system and cancellation stops being a nicety: it is the difference between paying for work users want and paying for work they walked away from.
Worse than the cost is the correctness hazard of the naive fix — terminating the work abruptly. If you cancel by tearing down the connection or killing the worker, you stop the agent at an arbitrary instruction. Maybe it had reserved inventory but not yet released it. Maybe it had written one of three files that must land together. Maybe it had told a sub-agent to charge a card and never learned whether the charge succeeded. Abrupt termination trades wasted compute for corrupted state, and corrupted state is far more expensive to detect and repair than the compute you saved. A system that cannot cancel cleanly is one that cannot cancel at all — it can only crash the task and hope.
Cooperative cancellation resolves this by making 'stop' a request the agent honors on its own terms. The client expresses intent; the agent chooses when and how to act on it, always at a point where its own invariants hold. This is the same discipline as cooperative scheduling or graceful shutdown: the running work is trusted to reach a consistent boundary before yielding, rather than being interrupted at a boundary it did not choose. The cost is that the agent author must design those boundaries; the payoff is that cancellation never leaves the system in a state the agent could not have reached on its own.
There is a second architectural reason cancellation earns its complexity: it converts an open-ended liability into a bounded, observable action. 'How long will this delegation run if the client loses interest?' has no good answer without cancellation — the honest reply is 'until it finishes or times out, whichever wastes more.' With a cancel path, the answer becomes 'until the next safe point, which we measure and bound with an SLO.' Anything with a bounded latency and a terminal state can be monitored, alarmed on, and reasoned about; an unbounded runaway cannot. Cancellation is how you put a ceiling on the cost of a client's change of mind.
The architecture: every piece explained
Top row: the request and the token. A client that wants to stop a delegation calls tasks/cancel with the task id it received when it first submitted the work. This is a plain request on the same JSON-RPC surface as tasks/send — cancellation is not a side channel but a first-class method. The remote agent receives the cancel intent and, rather than acting on it inline, records it against the task record: the task's state machine moves toward cancellation, and a cancel token associated with the running task is flipped. That token is the linchpin of the whole design — a single, shared, atomically-observable flag that the execution loop reads at every safe point. The cancel request returns quickly; it does not block until the work has actually stopped, because stopping cleanly may take time.
Middle row: the cooperative checks. The agent's running work — its chain of tool calls, model turns, and spawned sub-tasks — does not run as one uninterruptible blob. Between steps, at natural boundaries, the loop performs cooperative checks: before starting the next tool call, before the next model turn, after each sub-task returns, it reads the cancel token. If the token is set, the loop stops advancing and begins to wind down. The granularity of these checks is a direct tradeoff: check too rarely and cancel latency balloons (the agent finishes a two-minute tool call before it notices); check too eagerly and you add overhead and complexity. The right cadence puts a check before every expensive or externally-visible operation, so cancellation never has to wait through more than one bounded step.
The winding down is where compensation lives. An agent that has already produced side effects — a reserved seat, a half-written document, an open transaction — cannot simply stop; it must undo or settle what it started. Compensation is the explicit inverse of each committed step: release the reservation, delete the partial file, roll back or explicitly abort the transaction. This is the saga pattern applied to a single agent's work: every action that can be cancelled mid-flight registers how to compensate for itself, so unwinding is deterministic rather than improvised. Actions that are already durably committed and cannot be undone (an email sent, a payment captured) are not compensated away — they are recorded in the task's partial artifacts so the client learns exactly how far the work got before it stopped.
The right column and bottom rows: fan-out and settlement. A cancel signal that stops the top-level agent but leaves its sub-agents running has merely moved the leak one level down. So cancellation must propagate: when a parent task is cancelled, it issues tasks/cancel to every child delegation it spawned, and those propagate further, until the whole tree winds down. Once the agent's own work has settled and its children have acknowledged, the task reaches the terminal canceled state, annotated with a reason (client-requested, budget-exceeded, superseded) and any partial artifacts. The client is notified over the same status-update channel it was already watching — an SSE stream or a push webhook — so the resolution arrives on the transport the client is built to consume. The ops strip names the guarantees that make this safe in production: a cancel-latency SLO, an orphan sweep that catches sub-tasks whose parent died before propagating, idempotent handling of repeated cancels, and an audit trail of who cancelled what and why.
End-to-end flow
Trace a cancellation through a multi-agent research delegation. A client submits tasks/send with a request to compile a competitive analysis; the remote agent returns a task id and moves the task to working, streaming incremental status over SSE. Internally the agent has decomposed the job into three sub-delegations — one per competitor — each dispatched to a specialist sub-agent as its own A2A task.
The signal arrives: forty seconds in, the user navigates away and the client calls tasks/cancel(taskId). The remote agent receives the request, atomically flips the cancel token for that task, moves the task state toward canceling, and returns from the cancel call immediately. Note what it does not do: it does not block the caller while it cleans up, and it does not forcibly terminate anything. The token is set; the rest is cooperative.
The loop notices: the parent agent's orchestration loop was waiting on its three sub-tasks. At its next safe point — as the first sub-task returns and before it aggregates or launches follow-up work — it reads the cancel token and sees it set. It stops advancing. Rather than issue the next round of tool calls, it enters its wind-down path.
Fan-out propagates: the two sub-tasks still running are themselves A2A delegations, so the parent issues tasks/cancel to each. Those sub-agents receive the signal, flip their own tokens, check them at their next safe points, release any scratch resources (a vector-store query in flight, a temporary index), and settle into canceled, reporting back to the parent. The one sub-task that had already completed is left completed — its work is done and its artifact is retained as a partial result.
Compensation runs: the parent had opened a working document in a shared store to assemble the final report. On wind-down it deletes that partial document (its registered compensation) so no half-finished artifact leaks into the client's workspace. Anything durable it had already emitted — say, a cached intermediate summary — is recorded in the task's partial artifacts rather than deleted.
Settlement and notification: with children acknowledged and compensation complete, the parent moves the task to the terminal canceled state with reason client-requested, attaches the one completed competitor's analysis as a partial artifact, and emits a final status-update event on the SSE stream. The client — even though the user is gone — receives a clean terminal event it can log and reconcile. Total elapsed from cancel to terminal: a few hundred milliseconds, bounded by the slowest sub-task's safe-point interval. No tokens are spent after the signal; no resources leak; no state is left inconsistent.