Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [Design Overview](./design.md)
- [Protocol Reference](./protocol.md)
- [Request Cancellation](./request-cancellation.md)
- [Protocol V2](./protocol-v2.md)

# Conductor (agent-client-protocol-conductor)
Expand Down
106 changes: 106 additions & 0 deletions md/request-cancellation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Request Cancellation

The SDK exposes the ACP `$/cancel_request` notification behind the
`unstable_cancel_request` feature. The notification is protocol-level: either
side may send it to ask the peer to cancel one outstanding JSON-RPC request by
ID.

Enable the feature when depending on the crate:

```toml
agent-client-protocol = { version = "...", features = ["unstable_cancel_request"] }
```

To cancel a request sent through `ConnectionTo::send_request`, keep the
returned `SentRequest` and call `cancel` on it:

```rust
# use agent_client_protocol::{ConnectionTo, Error, UntypedRole};
# use agent_client_protocol_test::MyRequest;
# async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), Error> {
let request = cx.send_request(MyRequest {});
request.cancel()?;
# Ok(())
# }
```

The `SentRequest` remembers the peer and any proxy wrapping used for the
original request, so this also works for requests sent through
`ConnectionTo::send_request_to`.

Dropping a `SentRequest` before the SDK receives a response also sends
`$/cancel_request`. This covers abandoned request handles and futures. Once the
SDK routes a response to the waiting request handle, automatic cancellation is
disarmed, even if caller code has not yet consumed it with `block_task`,
`on_receiving_result`, or `forward_response_to`.

If you already have the JSON-RPC request ID, send the notification directly:

```rust
# use agent_client_protocol::{ConnectionTo, Error, UntypedRole};
# async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), Error> {
cx.send_cancel_request("request-id".to_string())?;
# Ok(())
# }
```

For incoming requests, get the request-local cancellation marker from the
`Responder`. This keeps cancellation handling next to the request work it
controls:

```rust
# use agent_client_protocol::{ConnectionTo, Error, Responder, UntypedRole};
# use agent_client_protocol_test::{MyRequest, MyResponse};
# async fn example(request: MyRequest, responder: Responder<MyResponse>, cx: ConnectionTo<UntypedRole>) -> Result<(), Error> {
# async fn run_request(_request: MyRequest) -> Result<MyResponse, Error> { todo!() }
let cancellation = responder.cancellation();

cx.spawn(async move {
let response = cancellation.run_until_cancelled(run_request(request)).await;
responder.respond_with_result(response)
})?;
Ok(())
# }
```

`run_until_cancelled` is the simple path for handlers that should stop work and
reply with the standard cancellation error as soon as cancellation is requested.
If the handler needs cleanup, partial results, or custom cancellation behavior,
use `cancellation.cancelled()` or `cancellation.is_cancelled()` directly inside
the request work instead.

Cancellation markers are only updated when the connection can process the
incoming `$/cancel_request` notification. Long-running handlers should return
quickly and move work into `ConnectionTo::spawn`, `SentRequest` callbacks, or
another task.

When proxying with `SentRequest::forward_response_to`, the SDK observes the
upstream `Responder` cancellation marker and forwards cancellation to the
downstream request automatically.

Register `CancelRequestNotification` or `ProtocolLevelNotification` directly
only when you need low-level access to cancellation notifications, such as
custom routing or protocol tracing:

```rust
# use agent_client_protocol::{ConnectionTo, Error, UntypedRole};
use agent_client_protocol::schema::CancelRequestNotification;

# fn builder() -> agent_client_protocol::Builder<UntypedRole> {
UntypedRole.builder()
.on_receive_notification(
async |cancel: CancelRequestNotification, _cx: ConnectionTo<UntypedRole>| {
let request_id = cancel.request_id;
// Mark the matching in-flight operation cancelled.
Ok(())
},
agent_client_protocol::on_receive_notification!(),
)
# }
```

Cancellation is cooperative. A peer may ignore `$/cancel_request`, may finish
with normal data, or may respond to the original request with
`Error::request_cancelled()` (`-32800`). The SDK ignores unhandled `$/...`
notifications so unsupported protocol-level notifications do not produce
method-not-found errors.
4 changes: 4 additions & 0 deletions src/agent-client-protocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- *(unstable)* Add SDK support for protocol-level request cancellation, including `SentRequest::cancel`, automatic cancellation when a `SentRequest` is dropped before receiving a response, request-local cancellation helpers on `Responder`, and forwarded cancellation propagation.

## [0.12.1](https://github.com/agentclientprotocol/rust-sdk/compare/v0.12.0...v0.12.1) - 2026-05-17

### Other
Expand Down
2 changes: 2 additions & 0 deletions src/agent-client-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ default = []
unstable = [
"unstable_auth_methods",
"unstable_boolean_config",
"unstable_cancel_request",
"unstable_logout",
"unstable_mcp_over_acp",
"unstable_message_id",
Expand All @@ -29,6 +30,7 @@ unstable = [
]
unstable_auth_methods = ["agent-client-protocol-schema/unstable_auth_methods"]
unstable_boolean_config = ["agent-client-protocol-schema/unstable_boolean_config"]
unstable_cancel_request = ["agent-client-protocol-schema/unstable_cancel_request"]
unstable_logout = ["agent-client-protocol-schema/unstable_logout"]
unstable_mcp_over_acp = ["agent-client-protocol-schema/unstable_mcp_over_acp"]
unstable_message_id = ["agent-client-protocol-schema/unstable_message_id"]
Expand Down
Loading