Skip to content

fix: conform the conductor and proxies to the proxy-chains RFD cleanup (#2743) and default v2 entity cancel to -32602 - #482

Merged
benbrandt merged 3 commits into
mainfrom
proxy-v2-conformance
Nov 2, 2026
Merged

benbrandt merged 3 commits into
mainfrom
proxy-v2-conformance

Conversation

@benbrandt

Copy link
Copy Markdown
Member

Implements the reference-conductor differences listed in agentclientprotocol/agent-client-protocol#2743 (proxy chains RFD cleanup), plus the v2 default error for tool_call/cancel and terminal/cancel. It was stacked on #480 and is now rebased onto main, including #481 (v1::schema / v2::schema paths). No public API changes.

Changes

1. The conductor rejects misplaced proxy/successor (fix(conductor))

proxy/successor is exchanged only between the conductor and the proxies it initialized, and between a conductor in proxy mode and its own conductor. The conductor used to forward a proxy/successor from its client (agent mode) to the first proxy, which treated it as coming from its successor, and one from the agent to the last proxy double-wrapped. Either side could forge messages that appear to come from the other.

The conductor now answers such a request with -32600 and drops and logs such a notification. The same applies to a proxy/successor wrapped twice, by a proxy or by the outer conductor of a conductor in proxy mode.

Tests (conductor/tests/proxy_wire.rs): agent mode (from the client, from the agent, double-wrapped from a proxy), proxy mode (double-wrapped from the outer conductor), and regressions showing legitimate proxy -> conductor and outer conductor -> proxy proxy/successor traffic still flows.

2. Proxies send $/cancel_request plain (fix(acp))

A proxy cancelling a request it sent to its successor used to wrap $/cancel_request in proxy/successor. Per #2743 it now sends it plain, with the ID of the proxy/successor request on that hop. SentRequest::cancel, RequestCancellationHandle::cancel and ConnectionTo::send_cancel_request_to always send it plain. Receivers, including the conductor, still accept the wrapped form, so a v5 conductor works with older proxies.

Tests: a two-proxy chain (v1 and v2) with tapped proxy connections asserts that every hop carries exactly one plain $/cancel_request naming that hop's request, and that the agent's request is cancelled. Another test checks that the conductor maps both a plain and a wrapped cancel from a proxy to the forwarded request, in agent mode and proxy mode. The existing core tests that expected the wrapped form now expect the plain one.

3. v2 default -32602 for tool_call/cancel and terminal/cancel (fix(acp))

An initialized Agent.v2() agent without a handler for these requests answers -32602 ("the tool call is unknown or not cancellable", with {toolCallId} / {terminalId} in data), matching the spec and the TypeScript SDK. A typed or catch-all handler takes precedence. Before initialization the usual -32600 applies. v1 and unguarded (Builder::new) agents still answer -32601. Testy v2 and simple_agent_v2 pick this up through the default.

Tests: unhandled -> -32602, plus -32600 before initialization; typed and catch-all handlers win; v1 (Testy) and raw agents still answer -32601; Testy v2 answers -32602.

Wire-format changes

  • $/cancel_request from a proxy to its conductor is now plain instead of wrapped in proxy/successor. The wrapped form is still accepted.
  • The conductor answers proxy/successor from the client (agent mode) or the agent with -32600 instead of forwarding it.
  • v2 agents answer unhandled tool_call/cancel / terminal/cancel with -32602 instead of -32601.

Docs: md/conductor.md, md/protocol.md, md/request-cancellation.md, md/protocol-v2.md, the cancellation rustdoc chapter, and migration notes in md/migration-v5.md.

benbrandt and others added 3 commits November 2, 2026 09:20
`proxy/successor` is exchanged only between the conductor and the proxies
it initialized, and between a conductor in proxy mode and its own
conductor. The conductor used to forward one from its client (agent mode)
to the first proxy, which treated it as coming from its successor, and one
from the agent to the last proxy double-wrapped. Either side could forge
messages that appear to come from the other.

The conductor now answers such a request with `-32600` and drops and logs
such a notification, as the proxy chains RFD requires. The same applies to
a `proxy/successor` wrapped in a second one by a proxy or by the outer
conductor of a conductor in proxy mode.

Co-Authored-By: Claude <noreply@anthropic.com>
`$/cancel_request` is hop-local. A proxy that cancels a request it sent to
its successor used to wrap the notification in `proxy/successor`. Per the
proxy chains RFD, senders should send it plain on the hop of the request it
cancels, with the ID of the `proxy/successor` request on that connection.
`SentRequest::cancel`, `RequestCancellationHandle::cancel` and
`ConnectionTo::send_cancel_request_to` now always send it plain.

Receivers, including the conductor, still accept the wrapped form and treat
it the same way, so a conductor works with proxies built on earlier
releases. The conductor maps the cancellation to the request it forwarded
on the next hop and re-issues it with that hop's ID.

Co-Authored-By: Claude <noreply@anthropic.com>
…th -32602

Neither method has an initialize-time capability. A Client sends one only
for a tool call or terminal that advertises `capabilities.cancel`, and the
v2 spec answers a request for an unknown, final or non-cancellable one with
`-32602`. An `Agent.v2()` agent without a handler for them used to answer
`-32601`. It now answers `-32602` with the `toolCallId` or `terminalId` in
`data`, matching the TypeScript SDK. A typed or catch-all handler still
takes precedence, and before initialization the usual `-32600` applies.

v1 and unguarded agents are unchanged: v1 has no such methods. Testy v2 and
`simple_agent_v2` answer `-32602` through the new default.

Co-Authored-By: Claude <noreply@anthropic.com>
@benbrandt
benbrandt enabled auto-merge (squash) November 2, 2026 08:32
@benbrandt
benbrandt merged commit d13e36b into main Nov 2, 2026
13 checks passed
@benbrandt
benbrandt deleted the proxy-v2-conformance branch November 2, 2026 08:33
@acp-release-bot acp-release-bot Bot mentioned this pull request Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant