Module 35: Cancellation and Timeouts
The two ways to cancel in ACP (session/cancel for a prompt turn, $/cancel_request for any request), graceful cancellation with RequestCancellation.cancelWhen, and the Java SDK’s own prompt timeouts: cancelGracePeriod and maxPromptDuration.
See the spec’s Cancellation page for the protocol rules this module builds on.
What You’ll Learn
- Why
session/canceldoesn’t end a turn by itself, and what happens to a prompt sent in between $/cancel_requestas a graceful cancel (cancelWhen) versus disposing the request’sMonocancelGracePeriodandmaxPromptDuration: Java SDK policy, not protocol, and why they exist- Writing a cooperative handler that checks for cancellation between steps
- Why a client-side request timeout now cancels the turn at a Java agent
promptTimeout(Duration): the client-side bound a prompt turn needs now thatrequestTimeoutno longer provides one
The Code
A deterministic agent, driven by the prompt text
session/cancel is a notification: the cancelHandler runs (here it just records the session id), but the turn is not over. It stays active until the cancelled prompt actually answers. A new prompt on that session sent before that happens is rejected with -32600.
A cooperative handler checks for cancellation itself and wraps up on its own terms:
StopReason.CANCELLED return above is good practice (it stops the loop
instead of ticking to 10 for nothing) but no longer the reason the turn ends in cancelled: once
session/cancel has been received for a prompt, the agent session sends cancelled regardless of
what the handler returns, or even throws, so slow could return PromptResponse.endTurn() in that
branch too and the client would still see cancelled.
A handler that ignores session/cancel entirely (stubborn, above) just keeps running: the SDK’s grace period is what eventually ends it.
Client: session/cancel
As of 0.80.0, send the prompt with a CancellationSignal and call stop.cancel() from any thread:
the client sends session/cancel for the prompt’s session, once, and the prompt still returns the
agent’s answer.
Client: $/cancel_request, graceful and immediate
cancelGracePeriod and maxPromptDuration
Neither exists in the protocol; this SDK adds them because 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 for good.
Duration.ZERO turns either off; exactly one answer is ever sent per prompt. Both are also available on AcpAgentSupport.Builder for annotated agents.
Document
cancelGracePeriod and maxPromptDuration as SDK policy, not protocol behavior. No other ACP SDK has an exact equivalent, and the spec defines neither.Three behavior changes worth knowing if you’re upgrading from 0.18.0:- A client-side request timeout now sends
$/cancel_request, so it actually cancels the work at a Java agent instead of leaving it running after the client gives up. - A prompt is no longer bounded by the client’s
requestTimeoutat all, since its answer comes only at the end of the turn, however long that takes. To put a bound back, setpromptTimeout(Duration)on the client builder (none by default); it fails the call with aTimeoutExceptionand sends$/cancel_request, the same as a plainrequestTimeoutused to. A one-off bound on a single prompt, without changing the client’s configuration, still works too:client.prompt(request).timeout(Duration.ofSeconds(5)). - The handler no longer needs to answer
cancelleditself. Oncesession/cancelhas been received for a prompt, the agent session sendscancelledregardless of what the handler returns (any stop reason) or throws (any exception, not justInterruptedException), keeping the handler’s own_metawhen it did return a response. Atry/catchwritten solely to turn a post-cancel failure intocancelledis now redundant, not wrong.