Skip to main content

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/cancel doesn’t end a turn by itself, and what happens to a prompt sent in between
  • $/cancel_request as a graceful cancel (cancelWhen) versus disposing the request’s Mono
  • cancelGracePeriod and maxPromptDuration: 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 that requestTimeout no 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:
As of 0.80.0, the explicit 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 requestTimeout at all, since its answer comes only at the end of the turn, however long that takes. To put a bound back, set promptTimeout(Duration) on the client builder (none by default); it fails the call with a TimeoutException and sends $/cancel_request, the same as a plain requestTimeout used 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 cancelled itself. Once session/cancel has been received for a prompt, the agent session sends cancelled regardless of what the handler returns (any stop reason) or throws (any exception, not just InterruptedException), keeping the handler’s own _meta when it did return a response. A try/catch written solely to turn a post-cancel failure into cancelled is now redundant, not wrong.

Source Code

View on GitHub

Running the Example

The demo runs all four scenarios with short timeouts (1s grace period, 3s max duration) so it finishes in a few seconds. No API key required.

Next Module

Module 36: Terminal Auth and Logout: an agent that requires sign-in, with two auth methods and a logout capability.