-
Notifications
You must be signed in to change notification settings - Fork 86
Per-request cancellation mechanism #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Per-request cancellation mechanism #183
Conversation
3af7d1f to
c0de9bb
Compare
c0de9bb to
82b5a5f
Compare
benbrandt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this a lot! I have a few questions, but I think it is a pretty strong start.
I'm also realizing we need to make it clearer how to update the schema, as right now it is derived from the Rust types (and you pointed out in your updates some holes we have in the schema that I should add around the basic JSON RPC stuff)
But I can also help with the Rust side, I think more important is we hash out some of these details anyway.
|
|
||
| The JSON-RPC specification doesn't define any standard mechanism for request cancellation and keeps it up to the implementation. | ||
|
|
||
| - The agent and the client **MUST** implement [$/cancelRequest](./schema#%24%2Fcancelrequest) notification method to support per-request cancellation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I think we may need to make this a capability given the number of in-progress agents/clients.
However, I think we could make this a "MUST" for a v2 of the protocol, which I think would make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I think we may need to make this a capability given the number of in-progress agents/clients.
However, I think we could make this a "MUST" for a v2 of the protocol, which I think would make sense.
Ok, no problem at all, so I'll describe it as a capability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For v1 at least 😄 I'll separately start a document we can collaborate on for v2 breaking change candidates
|
|
||
| - When a cancellation request is received from the remote side, the corresponding request activity and all nested activities (including outgoing requests) **MUST** be cancelled, then one of the responses **MUST** be sent back: | ||
| - an error response with the Cancelled [error code `-32800`](./schema#param-code) | ||
| - a valid response with the appropriate data (e.g., a partial result or a valid result with the Cancelled marker) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this also allow for the opposite side to also do some sort of graceful cancellation? Like we do for prompts where the agent can still send a session/update for currently in-progress items before responding with the cancelled stop reason?
Obviously the client would need to handle this as you pointed out in the line below, I'm just curious what the boundaries of both sides would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this also allow for the opposite side to also do some sort of graceful cancellation? Like we do for prompts where the agent can still send a session/update for currently in-progress items before responding with the cancelled stop reason?
Yes, the 2nd item is exactly to cover this. So if the handler handles a cancellation manually it may send a non-error response and the calling side will receive it. The 1st item covers the default case, when a handler execution is cancelled and not processed, in this case the default JsonRpcErrorCode.Cancelled should be sent
|
|
||
| - The request **MAY** be also cancelled from inside the request handler activity (e.g. when a heavy action is being performed in IDE by a request and the user cancels it explicitly). In this case the response with the [error code `-32800`](./schema#param-code) and the appropriate message **MUST** be sent back and the cancellation **SHOULD** be propagated on the calling side. | ||
|
|
||
| - Cancellation **MAY** also be done explicitly on a per-feature basis to cover the bigger scope of things to cancel (e.g., cancellation of a [prompt turn](./prompt-turn#cancellation)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In your mind, do you think we should move to a world where prompt cancellation would also fall under this cancellation paradigm?
I think it might fit, I'm just curious in your mind where you see the boundary of specialized cancellation vs generic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In your mind, do you think we should move to a world where prompt cancellation would also fall under this cancellation paradigm?
I think it might fit, I'm just curious in your mind where you see the boundary of specialized cancellation vs generic
TBH when I explored the prompt semantic I realized that it would be more handy (at least for us) to have prompt request only for the prompt start, not for the whole execution lifetime. It can make it more flexible.
Client Agent
->> [request] startPrompt(PromptStartRequest(initialMessage, additonalArgs...))
<<- [response] PromptStartResponse(promptId=xxx)
<<- [update] PromptUpdate(promptId=xxx, agentChunk)
<<- [update] PromptUpdate(promptId=xxx, tool_call)
....
<<- [update] PromptUpdate(promptId=xxx, PrompFinishEvent.END_TURN/CANCELLED/ETC...)
In this case you can bind all the updates to a particular prompt, because now it's not handy to maintaint something like current prompt, in interprocess/intermachine communication it may race.
|
|
||
| The Agent Client Protocol uses JSON RPC 2.0 for communication. | ||
|
|
||
| ### <span class="font-mono">JsonRpcRequest</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, we should expose this and make sure it is added to the schema
| The ID of the request to cancel. Must match the id from a previously sent | ||
| request. | ||
| </ResponseField> | ||
| <ResponseField name="method" type={"string"} required> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a payload here? Or just a request id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a payload here? Or just a request id?
Maybe to add a message? I can add arbitrary payload as well, but how to interpret it when received?
|
|
||
| The JSON-RPC specification doesn't define any standard mechanism for request cancellation and keeps it up to the implementation. | ||
|
|
||
| - The agent and the client **MUST** implement [$/cancelRequest](./schema#%24%2Fcancelrequest) notification method to support per-request cancellation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a common pattern to do a $ for a wildcard?
What do you think of other options like just cancel_request or request/cancel?
I'm open here, since we don't have one of this class yet, just curious the reasoning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a common pattern to do a $ for a wildcard?
What do you think of other options like justcancel_requestorrequest/cancel?
I'm open here, since we don't have one of this class yet, just curious the reasoning.
I decided to reuse the same name as in LSP. In my inderstanding the $ char means that the method belongs to the JsonRpc level (that is lower than ACP semantic level) unlike other methods that are explicitly bound to ACP entities. I don't mind to change it if you don't like it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good. Borrowing from LSP whenever possible makes sense to me!
| @@ -1,4 +1,7 @@ | |||
| { | |||
| "jsonRpcMethods": { | |||
| "cancel_request": "$/cancelRequest" | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting question: I wonder if we need to have agent/client methods to handle this.
Obviously not at the protocol level, but we do need to somehow route this to the agent/client
Which means the agent/client will need to keep track of which request body was attached to each request id. If we require the cancellation to pass more params, we are close to just defining individual cancellation requests...
🤔 interesting question. It might be fine to require on the SDK level that this is handled nicely for the implementer. But I think all the more reason to put this behind a capability for now, so we can also potentially roll this out initially as "unstable" and work out some of the wrinkles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which means the agent/client will need to keep track of which request body was attached to each request id. If we require the cancellation to pass more params, we are close to just defining individual cancellation requests...
I'm not sure about implementations in Rust or TS, but in Kotlin I maintatin a map in incoming requests, the map is [requestId->requestJob]. So while a request is being executed and cancel notification is received we know what exactly to cancel. BUT, it works exactly on JsonRpc level and doesn't touch semantic entities of ACP protocol, it's agnostic to what exactly should be cancelled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think we can definitely do that, but for the graceful cancellation I wonder if there is a way to route it... It would be possible though
This proposal covers per-JsonRPC request cancellation mechanism. The idea is taken from LSP.
The reason of adding per-request cancellation is to have cancellation on the JsonRPC level for any request without having specific cancellation methods for each feature/case.
Also, it fits well on async abilities of many languages (Kotlin, C#, probably JS/TS or Rust)
The description from the added cancellation.mdx
Now, I'm not sure if to strictly require each side to implement the
$/cancelRequestmethods. If to make it optional we have to writeif'sin code to support both cancellation approaches: per-request and per-feature (likesession/cancel). And, also we have to negotiate this in capabilities.