4.1 KiB
ADR-2026-08-20-acp-client-non-underscore-extension-methods: Route Non-Underscore Inbound Client Methods to the Extension Handler
Status: accepted Date: 2026-08-20 Area: backend, protocol
Context
ACP reserves the _-prefix namespace for extension methods, and the
kdlbs/acp-go-sdk fork only routes _-prefixed inbound methods to a peer's
ExtensionMethodHandler; every other unrecognized method falls through the
generated handle switch and returns -32601 method-not-found. Cursor breaks
that assumption: it delivers rich subagent metadata over an agent-to-client
request named cursor/task (no _ prefix), so Kandev, acting as the ACP
client, rejects it today and drops the subagent's prompt, model, description, and
agent id. Other current and future agent CLIs are likely to introduce their own
vendor-namespaced (vendor/method) inbound requests for the same reason, and the
adapter needs one consistent seam to accept or decline them rather than a
per-method fork patch each time.
Decision
Widen the SDK fork's client-side dispatch and let Kandev's ACP Client own the
accept/decline policy for unrecognized inbound methods.
- In
github.com/kdlbs/acp-go-sdkextensions.go,ClientSideConnection.handleWithExtensionsroutes any method the generatedClientSideConnection.handleswitch does not recognize to the client'sExtensionMethodHandlerwhen theClientimplements one, instead of only_-prefixed names. The_-prefix contract for the outboundCallExtension/NotifyExtensionhelpers (validateExtensionMethodName) is unchanged: Kandev only relaxes what it will accept, not what it will send. TheAgentSideConnectionpath is unchanged. A new pseudo-version is published and thereplaceinapps/backend/go.modis bumped to it. - Kandev's
Client(apps/backend/internal/agentctl/server/acp/client.go) implementsHandleExtensionMethod(ctx, method, params). It returns an empty success result only for methods it explicitly recognizes (cursor/taskfirst) and returnsacp.NewMethodNotFound(method)for everything else, so an unknown method still declines correctly rather than being silently accepted. - Parsing and correlation of a recognized method's params stay out of the SDK and
out of
client.go; they live in the agent-specific dialect (internal/agentctl/server/adapter/transport/acp/dialect_cursor.go), consistent with ADR 0044.
Consequences
- Kandev can accept vendor-namespaced inbound requests from any ACP agent CLI through one client-owned allowlist, without a fork patch per method.
- The accept/decline decision is centralized and explicit: an unrecognized method still returns method-not-found, so the change does not blanket-accept unknown traffic.
- The fork now diverges further from upstream
coder/acp-go-sdkon inbound client dispatch, so the divergence must be re-checked on every SDK rebase and is recorded here as the reason. - The relaxation is asymmetric (inbound accept only); outbound sends still refuse
non-
_names, preserving spec-compliant behavior toward peers.
Alternatives Considered
Wrap the transport instead of touching the fork (plan Option B)
Interpose a reader between agent stdout and NewClientSideConnection that peeks
frames, replies to cursor/task out-of-band on the shared writer, and passes
everything else through. Rejected: it duplicates the SDK's JSON-RPC framing and
response correctness, adds a second code path that must stay in lockstep with the
SDK, and is higher risk than a small, well-scoped fork change we already own.
Ask Cursor to use a _-prefixed method
Not actionable: cursor/task is Cursor's wire contract and Kandev does not
control it. The client must adapt to observed agent behavior.
Handle cursor/task with a hardcoded branch in the SDK fork
Rejected: it embeds one vendor's method name in a protocol library and forces a fork bump for the next agent that does the same. Routing unrecognized methods to the client keeps vendor knowledge in Kandev's dialect layer.