Draft: Task Event Sources#2
Conversation
|
|
||
| ## Motivation | ||
|
|
||
| [SEP-2663](https://modelcontextprotocol.io/seps/2663-tasks-extension) provides no mechanism for delivering intermediate output to the model during task execution. The model's only visibility into an in-progress task is the `statusMessage` field — a single optional string observable at poll time. For tasks executing over minutes to hours, the model cannot present partial findings, adjust its reasoning based on what has been discovered, or decide to cancel a task producing irrelevant results. |
There was a problem hiding this comment.
I think there's two orthogonal things here:
- We want tasks to support intermediate results in some form (progress, intermediate artifacts like a single build step completed, etc.)
- We want tasks to support server push to reduce latency of information updates (compared to today's only option, which is polling)
I feel integrating with events is a solution to (2), and should be separate from (1). Why? We should support intermediate results without needing events: you should be able to get intermediate results via polling and then events are purely a latency optimisation if the client+server can support the push/webhook events delivery mode.
There was a problem hiding this comment.
I feel we might need to take a step back and spec out how we want intermediate results to work for tasks more generally? Is it actually a stream of events, or is it more like a state machine? Events can be lossy whereas a state machine always has a "current state" that you can always retrieve via polling. I can see cases for both, which is why it may be good to explore the problem space a bit more to make sure we have the right abstraction.
There was a problem hiding this comment.
The main property driving this proposal is that events are expected to be delivered to the model, and therefore if those events are not useful context, events should not be used to send them. A task is a state machine that the application needs to poll and only really cares about its current state, while events are a stream that the model needs to observe, as it needs to understand how the underlying work evolves.
Not to mention that the model is generally not supposed to be able to poll a task on-demand directly, at least not as the primary mode of interacting with it. If someone wants that, they can expose a tool that accepts the task ID or similar to return the current state in some form that is meaningful to the model itself (or, if an application developer really wants the agent in the loop at all times, it can be an application-level tool instead).
| "input": { | ||
| "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" | ||
| } |
There was a problem hiding this comment.
We should also provide the initial cursor since cursor: null would mean "start from latest" and the client might miss something if it were published between task creation and initial subscribe.
There was a problem hiding this comment.
Adding to this - on stateless deployments where Events push mode isn't viable (no persistent push channel) and clients fall back to poll, the initial-cursor requirement is more significant no?
In the time between tools/call --> CreateTaskResult and the client's first events/poll, the round-trip is sadly unavoidable iiuc. So without an initial cursor the client has to either replay-from-zero (if the source is cursored and retains history) or put up with the fact that events emitted in that window are lost (if cursorless). So for tasks that emit a single early event that is of high value (e.g. CI's first stage finishing very fast), the "lost window" case is observable. So may be we could make this concrete and say "TaskEventSource SHOULD carry the cursor the client should resume from. Same shape as the Events primitive's events/subscribe response."
|
One q I wanted to clarify wrt SEP-2663's in-task input flow (TaskElicit / TaskSample) - Both mechanisms trigger (pardon the pun) server-side activity/intent to the client mid-task, but they have opposite semantics. TaskElicit is request-and-await, while eventSource would be fire-and-forget (emit, no response expected, task keeps running). Though these compose orthogonally in principle i think it would worthwhile Worth pinning down in the spec text either as a "see also" or a one-line clarification Something liek "eventSource MUST NOT be used to gather input that the task's continuation depends on - that's inputRequests / tasks/update's contract. Event payloads are observational and they don't pause the task." Otherwise it's easy for an implementer to misuse events as a substitute for TaskElicit and end up with a stuck task whose 'response' never arrives because nothing is listening for it. |
|
@panyam Elicitation is aimed at the end-user rather than the model - is it possible to use Consider a tool that returns a task with an event source, and that event source's payloads carry the task ID - an additional tool could accept that task ID as a regular parameter (this would be an explicit state handle a la SEP-2567) and do something along the lines of SEP-2669's proposed |
Co-authored-by: Peter Alexander <pja@anthropic.com>
Motivation and Context
Introduces a draft proposal explaining how Tasks and Events can integrate with each other to support intermediate result delivery. Intended to spur discussion for now.
This SEP extends
CreateTaskResult(defined by SEP-2663) with an optionaleventSourcefield that associates a task with an event stream from the MCP Events primitive. A client that receives aCreateTaskResultcontainingeventSourcesubscribes to the named event using the provided input parameters, receiving intermediate results routed to the model while the task executes in the background. The server terminates the event stream when the task reaches a terminal status.🤖 Generated by cajoling Claude Code into writing something sensible.