$/cancel_request,
cooperative cancellation, -32800, and the specâs own cascading-cancellation diagram) and
Prompt Turn: Cancellation
(session/cancel, the cancelled stop reason).
The two mechanisms
session/cancel (a notification) asks the agent to end the current prompt turn. The turn stays
active until the cancelled prompt actually answers: a new prompt sent in between gets -32600
(invalid request), the same as any prompt sent while another is already running on that session. The
agent session answers cancelled once session/cancel has been received for the prompt, whatever the
handler itself returns or throws; see below.
$/cancel_request cancels any single in-flight request, in either direction. Disposing a
requestâs Mono (directly, via .timeout(...), or the SDKâs own request timeout, 60 seconds by
default on both sides) sends $/cancel_request once, after the request was written; the callerâs
Mono ends immediately and a late answer from the peer is discarded. A prompt turn is the one
exception: its answer isnât bounded by the request timeout at all, only by promptTimeout if one is
set.
On a client, stop a prompt turn and still get the agentâs answer (stop reason cancelled, with the
turnâs updates) with a CancellationSignal, as of 0.80.0:
AcpSyncClient.prompt(request, stop) takes the same signal, blocking. Calling stop.cancel() after
the answer already arrived sends nothing. This replaces the only other way to do this before 0.80.0,
contextWrite(RequestCancellation.cancelWhen(trigger)), which no completion menu offered and the sync
client couldnât use at all:
RequestCancellation/contextWrite still exists, for $/cancel_request on any other request type
(not a prompt), where a CancellationSignal parameter isnât offered.
Java SDK policy: the grace period and maxPromptDuration
Neither of these exists in the protocol, and no peer SDK has an equivalent (the closest precedent is
an undocumented 1-second graceful-cancel timeout in the Kotlin SDK). They exist for a structural
reason: the spec requires every cancelled prompt to be answered cancelled, and a session allows
only one active turn, so a handler that never answers would leave its session busy forever with
nothing able to recover it.
- Cancel grace period (
PromptTimeouts.cancelGracePeriod, default 60 seconds): if the handler hasnât answered this long aftersession/cancel, the SDK cancels the handlerâs subscription (interrupting its thread, for a sync handler) and answerscancelledon its behalf. maxPromptDuration(PromptTimeouts.maxPromptDuration, off by default): a hard ceiling on one turnâs length, answered-32800with data{"maxPromptDuration": "<ISO-8601>"}when it fires (orcancelled, if the prompt was already being cancelled).
Duration.ZERO disables either one. Exactly one answer is ever sent per prompt. Document both of
these as SDK policy, not protocol behavior: theyâre specific to this implementation.
Noticing a cancellation from inside a @Prompt method
A @Prompt handler finds out about a cancellation the same way regardless of which of the four
triggers caused it (session/cancel or session/close for its session, $/cancel_request for its
own request, or the agent itself ending it: the cancel grace period or maxPromptDuration passing,
or the connection closing):
SyncPromptContext.isCancelled() is a poll: check it between steps of a long-running handler, to stop
early rather than finish work nobody will see the result of. For work you canât poll, such as a
subprocess or an outstanding HTTP call that needs aborting, onCancel(Runnable) registers a callback
that runs once, on whichever thread delivers the cancellation, so it must be quick and must not block.
The async PromptContext.whenCancelled() returns a Mono<Void> that completes (empty) on
cancellation, for composing into a Reactor pipeline, for example
work.takeUntilOther(context.whenCancelled()) to stop a chain of operators.
The handler doesnât need to choose cancelled itself, or even succeed. Once session/cancel has
been received for a prompt, the agent session sends cancelled as the answer no matter what the
handler returns (PromptResponse.endTurn() included) or throws, keeping the handlerâs own _meta when
it returned a response. A handler that fails after a cancel, for whatever reason, no longer needs its
own try/catch to avoid leaking -32603: the failure is answered cancelled too. Before
session/cancel arrives, a handlerâs own failure is still answered as any other failure would be; this
only applies once the prompt is already being cancelled. The early-exit pattern above is still worth
writing, to stop doing work nobody will see the result of, but the specific stop reason it returns no
longer matters.
Once isCancelled() is true, it stays true. After $/cancel_request (as opposed to session/cancel),
the SDK has already answered and interrupted the handlerâs thread, so whatever the handler eventually
returns is simply discarded; that case is unaffected by the paragraph above.
The full sequence
Steps labeled âJava SDK policyâ (B and C above) are this SDKâs behavior, not the protocolâs; the rest follows the spec directly.Whatâs unchanged, and whatâs new
The agent sideâs incoming notification handlers are not ordered relative to responses, andsession/cancel takes effect on the inbound thread before any later message is handled.
A behavior change worth knowing if youâre upgrading: a client-side prompt timeout now actually
cancels the turn at a Java agent (it used to let the agent keep running after the client gave up).
Raise requestTimeout for genuinely long prompts if the old behavior was load-bearing anywhere.
Another behavior change, more recent still: a prompt handler used to need to notice
session/cancel and explicitly answer cancelled itself; a handler that returned another stop reason
(or failed) after a cancel sent that instead, -32603 included. The agent session now sends cancelled
regardless of what the handler returns or throws, once session/cancel has been received for that
prompt. Code written against the old behavior (an explicit try/catch around the handler body solely
to turn a post-cancel failure into cancelled) still works, since catching and returning cancelled
by hand is simply redundant now, not wrong.
Related
Concepts
The prompt turn, and why session/cancel doesnât end it by itself
Migration Guide
The full session/cancel behavior change from 0.18.0