From 4cf598dfef81139f0d1c73051e8cef74f45a40a7 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:24:28 -0700 Subject: [PATCH 01/13] SEP: Task Interaction Methods (steer, pause, resume) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the MCP Tasks extension (SEP-2663) with three methods for interacting with running tasks: - tasks/steer: unsolicited mid-run feedback (ack-only) - tasks/pause: cooperative halt with paused status - tasks/resume: continue from paused Discussed with @LucaButBoring in #2663. Production implementation deployed with reference data (steer reduced cancellation ~40%). File uses 0000 placeholder — will rename to PR# after creation. --- docs/seps/0000-task-interaction.mdx | 248 +++++++++++++++++++++ seps/0000-task-interaction.md | 319 ++++++++++++++++++++++++++++ 2 files changed, 567 insertions(+) create mode 100644 docs/seps/0000-task-interaction.mdx create mode 100644 seps/0000-task-interaction.md diff --git a/docs/seps/0000-task-interaction.mdx b/docs/seps/0000-task-interaction.mdx new file mode 100644 index 000000000..e22286135 --- /dev/null +++ b/docs/seps/0000-task-interaction.mdx @@ -0,0 +1,248 @@ +--- +title: "SEP-{NUMBER}: Task Interaction Methods" +sidebarTitle: "SEP-{NUMBER}: Task Interaction Methods" +description: "Task Interaction Methods" +--- + +
+ + Draft + + + Extensions Track + +
+ +| Field | Value | +| ------------- | ------------------------------------------------------------------------------- | +| **SEP** | {NUMBER} | +| **Title** | Task Interaction Methods | +| **Status** | Draft | +| **Type** | Extensions Track | +| **Created** | 2026-04-30 | +| **Author(s)** | Pedram Rezaei ([@prezaei](https://github.com/prezaei)) | +| **Sponsor** | None (seeking sponsor) | +| **PR** | [#{NUMBER}](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/{NUMBER}) | + +--- + +## Abstract + +This SEP extends the MCP Tasks extension (SEP-2663) with three methods for interacting with running tasks: `tasks/steer` (unsolicited feedback), `tasks/pause` (cooperative halt), and `tasks/resume` (continue from paused). Together, these enable human-in-the-loop and agent-in-the-loop patterns for long-running task execution — the ability to redirect, pause, and resume work without cancelling and losing accumulated state. + +These methods follow the design patterns established by SEP-2663: they use the reserved `tasks/` method prefix, carry `taskId` as the routing key, and respect the consistency model (ack-only writes, cooperative operations, capability negotiation). + +## Motivation + +SEP-2663 provides the foundation for durable, asynchronous task execution: create a task, poll for status, deliver input when requested, cancel if needed. This covers the lifecycle, but treats the running task as a closed box — the only client interactions are responding to server-initiated input requests (`tasks/update`) and terminating (`tasks/cancel`). + +For subagent-as-a-service — where a parent agent or human user delegates work to a specialist agent consumed over MCP — two interaction patterns are missing: + +### 1. Mid-run redirection (steering) + +Once a task starts, there is no way for a user or parent agent to provide unsolicited feedback. The only input path is `tasks/update`, which responds to specific `inputRequests` the server issued. If a user watching a research agent wants to say "focus on academic sources" or "skip the unit tests," they must cancel the task and start over, losing all accumulated context and partial results. + +This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (Claude Code, GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. + +### 2. Pause and resume + +Long-running tasks (browser automation, multi-step research, code generation) often reach a point where continuation should be deferred — the user wants to review partial output before continuing, a billing checkpoint has been reached, or the server needs to hold resources (e.g., a browser VM) without actively computing. + +Today the only options are "let it run" or "cancel and lose everything." Cancel is destructive — it discards accumulated context, partial results, and in-flight computation. A research agent 20 minutes into a deep investigation shouldn't lose everything because the user wants to think. Pause preserves state. Resume continues from where it left off. + +Server-initiated pause is equally important: a browser automation server that has completed the user's ask but wants to hold the VM for potential follow-up has no way to signal "I'm done for now but available" without either completing (losing the VM) or staying in `working` indefinitely (confusing monitoring and billing). + +### Why not use existing primitives? + +- **`tasks/update` for steering?** `tasks/update` delivers `inputResponses` keyed to specific `inputRequests` the server issued. Steering is unsolicited — the user is providing new direction the server didn't ask for. Putting steering into `tasks/update` would require the server to issue a permanent open-ended `inputRequest` just to receive feedback, which is a protocol anti-pattern. + +- **`tasks/cancel` + restart for pause?** Cancel is destructive. It discards accumulated state and signals "I don't want this anymore." Pause signals "I want to keep this but not right now." These are fundamentally different semantics. + +- **Custom tools for steering?** Tools are designed for LLM-initiated actions, not human-initiated feedback. Protocol-level steering gives harnesses (IDEs, web UIs) a standard way to forward user feedback without routing through the model. + +## Specification + +### Extension Identifier + +This extension builds on `io.modelcontextprotocol/tasks` (SEP-2663). It adds sub-capabilities within the Tasks extension: + +```jsonc +{ + "capabilities": { + "extensions": { + "io.modelcontextprotocol/tasks": { + "steer": true, + "pause": true // implies resume + } + } + } +} +``` + +### `tasks/steer` + +Send unsolicited feedback from a parent agent or user to a running task. + +#### Request + +```typescript +interface TaskSteerRequest extends JSONRPCRequest { + method: "tasks/steer"; + params: { + taskId: string; + message: string; + }; +} +``` + +#### Response + +```typescript +type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" +``` + +#### Behavioral Requirements + +- **Ack-only, eventually consistent.** Same pattern as `tasks/update` and `tasks/cancel`. +- **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point. +- **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. +- **Accepted on `working` and `paused` tasks.** Messages queue for delivery when the task resumes. +- **Rejected on terminal tasks** with `-32602` (Invalid params). +- **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`. +- **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. + +### `tasks/pause` + +Request cooperative halt of a running task at the next safe point. + +#### Request + +```typescript +interface TaskPauseRequest extends JSONRPCRequest { + method: "tasks/pause"; + params: { + taskId: string; + }; +} +``` + +#### Response + +```typescript +type TaskPauseResult = Result & DetailedTask; // resultType: "complete" +``` + +#### Behavioral Requirements + +- **Cooperative.** Servers that don't support pause return `-32601`. Servers that can't pause at the current point return the current `DetailedTask` with status unchanged. +- **Reachable from `working` or `input_required`.** Other statuses return `-32602`. +- **A `paused` task accepts:** `tasks/steer` (queued), `tasks/cancel`, `tasks/resume`, `tasks/get`. +- **A `paused` task does NOT accept:** `tasks/update`, `tasks/pause`. +- **Server-initiated pause.** The server MAY transition to `paused` without a client request. +- **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. + +### `tasks/resume` + +Resume execution of a paused task. + +#### Request + +```typescript +interface TaskResumeRequest extends JSONRPCRequest { + method: "tasks/resume"; + params: { + taskId: string; + }; +} +``` + +#### Response + +```typescript +type TaskResumeResult = Result & DetailedTask; // resultType: "complete" +``` + +#### Behavioral Requirements + +- **Paired with `tasks/pause`.** If a server supports pause, it MUST support resume. +- **Only valid from `paused` status.** Other statuses return `-32602`. +- **Post-resume state is server-determined.** +- **Queued steer messages are delivered after resume.** +- **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. + +### Task Status: `paused` + +Adds `paused` to the task status values: + +```typescript +interface PausedTask extends Task { + status: "paused"; +} +``` + +| Property | Value | +|----------|-------| +| Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | +| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`) | +| Terminal | No | + +### Error Summary + +| Error | Code | Method | When | +|-------|------|--------|------| +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in terminal state | + +## Rationale + +### Why ack-only for `tasks/steer` but `DetailedTask` for pause/resume? + +SEP-2663's rationale for ack-only `tasks/update`: "there is no read data the server needs to return that the client cannot get from a follow-up `tasks/get`." This applies to `tasks/steer` — the steer is queued and unprocessed at ack time. + +For pause/resume, the client needs immediate feedback: did the server actually pause? Returning `DetailedTask` provides this without an additional `tasks/get` round-trip. + +### Why a `paused` status instead of a flag? + +A separate status makes the state machine explicit. Clients can pattern-match on `task.status` without inspecting auxiliary fields. It also makes transition rules clear — `paused` accepts different methods than `working`. + +### Why allow server-initiated pause? + +Client-only pause covers "user wants to think." Server-initiated pause covers resource management: holding a browser VM after completing an action, billing checkpoints, concurrency throttling. Without it, these use cases require staying in `working` indefinitely (misleading) or completing and losing state (destructive). + +## Backward Compatibility + +Fully backward compatible with SEP-2663. No changes to existing methods, status values, or transitions. The `paused` status is additive. Capability negotiation ensures no silent failures. + +## Security Implications + +- **Steer message content:** Servers MUST treat steer messages with the same trust model as tool arguments — user-supplied input that may contain injection attempts. +- **Steer queue exhaustion:** Servers SHOULD enforce a queue depth limit to prevent denial of service. +- **Paused resource holding:** Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. +- **Handle security:** Same model as SEP-2663 — authenticated servers validate `(taskId, principal)` on every call. + +## Reference Implementation + +A complete implementation is deployed in production in the [Monet repository](https://microsoft.ghe.com/bic/monet) (`libs/monet-mcp`, `monet_mcp.tasks` subpackage). Key details: + +- `tasks/steer`: per-task `asyncio.Queue` for message delivery +- `tasks/pause`: out-of-band pause state tracking via `TaskExtensionState`, cooperative halt via `asyncio.Event` at `safe_yield()` points +- All methods registered as custom MCP request handlers + +Production findings: steer reduced task cancellation-and-restart by ~40%; server-initiated pause enabled browser automation VM holding. + +## Open Questions + +1. Should the spec define a SHOULD-level default steer queue depth (e.g., 16 messages)? +2. When a task pauses from `input_required` and resumes, are the pending `inputRequests` still valid? +3. Should extension methods carry `requestState` for stateless server routing? + +## Acknowledgments + +- Ryan Nowak — original extensions proposal +- Luca Chang (@LucaButBoring) — SEP-2663 design patterns +- Caitie McCaffrey (@CaitieM20) — Agents WG and tasks stabilization +- Peter Alexander (@pja-ant) — `requestState` analysis +- Claude Code — AI assistance used for cross-referencing SEPs, adversarial review, and drafting. All design decisions were human-directed. diff --git a/seps/0000-task-interaction.md b/seps/0000-task-interaction.md new file mode 100644 index 000000000..ffb852696 --- /dev/null +++ b/seps/0000-task-interaction.md @@ -0,0 +1,319 @@ +# SEP-{NUMBER}: Task Interaction Methods + +- **Status**: Draft +- **Type**: Extensions Track +- **Created**: 2026-04-30 +- **Author(s)**: Pedram Rezaei (@prezaei) +- **Sponsor**: None (seeking sponsor — @CaitieM20 / @LucaButBoring from the Agents WG are natural fits) +- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/{NUMBER} + +## Abstract + +This SEP extends the MCP Tasks extension (SEP-2663) with three methods for interacting with running tasks: `tasks/steer` (unsolicited feedback), `tasks/pause` (cooperative halt), and `tasks/resume` (continue from paused). Together, these enable human-in-the-loop and agent-in-the-loop patterns for long-running task execution — the ability to redirect, pause, and resume work without cancelling and losing accumulated state. + +These methods follow the design patterns established by SEP-2663: they use the reserved `tasks/` method prefix, carry `taskId` as the routing key, and respect the consistency model (ack-only writes, cooperative operations, capability negotiation). + +## Motivation + +SEP-2663 provides the foundation for durable, asynchronous task execution: create a task, poll for status, deliver input when requested, cancel if needed. This covers the lifecycle, but treats the running task as a closed box — the only client interactions are responding to server-initiated input requests (`tasks/update`) and terminating (`tasks/cancel`). + +For subagent-as-a-service — where a parent agent or human user delegates work to a specialist agent consumed over MCP — two interaction patterns are missing: + +### 1. Mid-run redirection (steering) + +Once a task starts, there is no way for a user or parent agent to provide unsolicited feedback. The only input path is `tasks/update`, which responds to specific `inputRequests` the server issued. If a user watching a research agent wants to say "focus on academic sources" or "skip the unit tests," they must cancel the task and start over, losing all accumulated context and partial results. + +This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (Claude Code, GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. + +### 2. Pause and resume + +Long-running tasks (browser automation, multi-step research, code generation) often reach a point where continuation should be deferred — the user wants to review partial output before continuing, a billing checkpoint has been reached, or the server needs to hold resources (e.g., a browser VM) without actively computing. + +Today the only options are "let it run" or "cancel and lose everything." Cancel is destructive — it discards accumulated context, partial results, and in-flight computation. A research agent 20 minutes into a deep investigation shouldn't lose everything because the user wants to think. Pause preserves state. Resume continues from where it left off. + +Server-initiated pause is equally important: a browser automation server that has completed the user's ask but wants to hold the VM for potential follow-up has no way to signal "I'm done for now but available" without either completing (losing the VM) or staying in `working` indefinitely (confusing monitoring and billing). + +### Why not use existing primitives? + +- **`tasks/update` for steering?** `tasks/update` delivers `inputResponses` keyed to specific `inputRequests` the server issued. Steering is unsolicited — the user is providing new direction the server didn't ask for. Putting steering into `tasks/update` would require the server to issue a permanent open-ended `inputRequest` just to receive feedback, which is a protocol anti-pattern: it conflates "I need specific information to proceed" with "I accept general feedback at any time." + +- **`tasks/cancel` + restart for pause?** Cancel is destructive. It discards accumulated state and signals "I don't want this anymore." Pause signals "I want to keep this but not right now." These are fundamentally different semantics that should not be overloaded on the same method. + +- **Custom tools for steering?** A server could expose a `steer_task` tool, but tools are designed for LLM-initiated actions, not human-initiated feedback. The parent agent would need to decide to call the tool, adding unnecessary indirection. Protocol-level steering gives harnesses (IDEs, web UIs) a standard way to forward user feedback without routing through the model. + +## Specification + +### Extension Identifier + +This extension builds on `io.modelcontextprotocol/tasks` (SEP-2663). It does not define a new top-level extension — instead, it adds sub-capabilities within the Tasks extension: + +```jsonc +{ + "capabilities": { + "extensions": { + "io.modelcontextprotocol/tasks": { + "steer": true, + "pause": true // implies resume + } + } + } +} +``` + +Clients discover support via `server/discover`. Clients that call an unsupported method receive `-32601` (Method not found). + +### `tasks/steer` + +Send unsolicited feedback from a parent agent or user to a running task. The message is queued and delivered at the next server-determined checkpoint where the task can safely accept external input. + +#### Request + +```typescript +interface TaskSteerRequest extends JSONRPCRequest { + method: "tasks/steer"; + params: { + /** + * ID of the task to steer. + */ + taskId: string; + + /** + * Natural language feedback or instruction. + * Delivered at the next server-determined safe point. + */ + message: string; + }; +} +``` + +#### Response + +```typescript +type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" +``` + +#### Behavioral Requirements + +- **Ack-only, eventually consistent.** Follows the same pattern as `tasks/update` and `tasks/cancel`. The server acks immediately; the steer message is queued for delivery at the next safe point. +- **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point — for an LLM-based agent it may be between inference steps or tool calls; for a pipeline it may be between stages; for a batch job it may be at explicit checkpoints. The server defines this based on its execution model. +- **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. There is no protocol-defined queue depth limit; servers SHOULD document their limits. +- **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. +- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer`. The error code is `-32602` (Invalid params). +- **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`, the server MUST ack even for invalid or nonexistent task IDs. + +#### Streamable HTTP + +The `Mcp-Name` header MUST be set to `params.taskId`, consistent with other task methods. + +### `tasks/pause` + +Request cooperative halt of a running task at the next safe point. Adds a `paused` status to the task lifecycle. + +#### Request + +```typescript +interface TaskPauseRequest extends JSONRPCRequest { + method: "tasks/pause"; + params: { + /** + * ID of the task to pause. + */ + taskId: string; + }; +} +``` + +#### Response + +```typescript +type TaskPauseResult = Result & DetailedTask; +``` + +The `resultType` field MUST be `"complete"`. The response carries the current `DetailedTask` so the client knows whether the server actually transitioned to `paused` or could not pause at this point. + +#### Behavioral Requirements + +- **Cooperative.** The server is not required to support pause. Servers that do not support it MUST return `-32601` (Method not found). Servers that support it but cannot pause at the current execution point SHOULD return the current `DetailedTask` with status unchanged. +- **Reachable from `working` or `input_required`.** Pausing from other statuses MUST return `-32602` (Invalid params). +- **Safe point halt.** The server halts at the next server-determined checkpoint, same as `tasks/steer` delivery points. +- **A `paused` task accepts:** `tasks/steer` (queued for delivery on resume), `tasks/cancel`, `tasks/resume`, `tasks/get`. +- **A `paused` task does NOT accept:** `tasks/update` (resume first, then provide input), `tasks/pause` (already paused — return `-32602`). +- **Server-initiated pause.** The server MAY transition a task to `paused` without a client `tasks/pause` request. Use cases include: resource management (holding a VM after completing the user's ask), billing checkpoints, or concurrency throttling. The client discovers the transition via `tasks/get` polling or `notifications/tasks/status`. + +#### Streamable HTTP + +The `Mcp-Name` header MUST be set to `params.taskId`. + +### `tasks/resume` + +Resume execution of a paused task. + +#### Request + +```typescript +interface TaskResumeRequest extends JSONRPCRequest { + method: "tasks/resume"; + params: { + /** + * ID of the task to resume. + */ + taskId: string; + }; +} +``` + +#### Response + +```typescript +type TaskResumeResult = Result & DetailedTask; +``` + +The `resultType` field MUST be `"complete"`. The response carries the `DetailedTask` with the post-resume status (typically `working` or `input_required` if input was pending before pause). + +#### Behavioral Requirements + +- **Cooperative.** Same as `tasks/pause` — servers that don't support it return `-32601`. +- **Paired with `tasks/pause`.** If a server supports `tasks/pause`, it MUST also support `tasks/resume`. +- **Only valid from `paused` status.** Resuming from any other status MUST return `-32602` (Invalid params). +- **Post-resume state is server-determined.** The server transitions to whatever status is appropriate. The client polls `tasks/get` for subsequent updates. +- **Queued steer messages are delivered after resume.** Any `tasks/steer` messages queued during the paused state are delivered at the next safe point after execution resumes. + +#### Streamable HTTP + +The `Mcp-Name` header MUST be set to `params.taskId`. + +### Task Status: `paused` + +This extension adds `paused` to the task status values: + +```typescript +type TaskStatus = "working" | "input_required" | "completed" | "cancelled" | "failed" | "paused"; +``` + +| Property | Value | +|----------|-------| +| Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | +| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`) | +| Terminal | No | + +A `PausedTask` variant is added to `DetailedTask`: + +```typescript +interface PausedTask extends Task { + status: "paused"; +} + +type DetailedTask = + | WorkingTask + | InputRequiredTask + | CompletedTask + | FailedTask + | CancelledTask + | PausedTask; +``` + +### Task Status Notifications + +`notifications/tasks/status` MAY carry the `paused` status, following the same delivery rules as other status transitions (on the `tasks/get` SSE stream for Streamable HTTP). + +### Error Summary + +| Error | Code | Method | When | +|-------|------|--------|------| +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | + +`tasks/steer` silently acks for invalid `taskId`, consistent with `tasks/update` and `tasks/cancel`. + +## Rationale + +### Why ack-only for `tasks/steer` but `DetailedTask` for pause/resume? + +SEP-2663's rationale for ack-only `tasks/update`: "there is no read data the server needs to return that the client cannot get from a follow-up `tasks/get`, and forcing an embedded Task into the response would re-introduce the non-idempotency we are trying to avoid." + +This reasoning applies directly to `tasks/steer` — the steer is queued and unprocessed at ack time, so the returned state would be identical to a `tasks/get` and misleading about the steer's effect. + +For `tasks/pause` and `tasks/resume`, the situation differs: the client needs immediate feedback on whether the cooperative operation succeeded. Did the server actually pause? Or is it still running because it couldn't reach a safe point? Returning `DetailedTask` provides this signal without an additional `tasks/get` round-trip. This is analogous to HTTP `DELETE` returning `200` with the resource vs `204`. + +### Why not extend `tasks/update` for steering? + +`tasks/update` has specific semantics: it delivers `inputResponses` keyed to `inputRequests` the server issued. The keys are idempotency tokens unique over the task lifetime. Steering has none of these properties — it's unsolicited, unkeyed, and not idempotent (sending the same steer message twice should deliver it twice). Mixing these semantics would complicate both client and server implementations. + +### Why a `paused` status instead of a flag on `working`? + +A separate status makes the state machine explicit and enumerable. Clients can pattern-match on `task.status` without inspecting auxiliary fields. It also makes the transition rules clear — `paused` accepts different methods than `working` (`tasks/resume` but not `tasks/update`). + +### Why allow server-initiated pause? + +Client-only pause covers the "user wants to think" case. Server-initiated pause covers resource management: a browser automation server holds the VM after completing an action, a billing system checkpoints at cost thresholds, a scheduler throttles concurrent tasks. Without server-initiated pause, these use cases require either staying in `working` indefinitely (misleading) or completing and losing state (destructive). + +## Backward Compatibility + +This extension is **fully backward compatible** with SEP-2663: + +- No changes to existing methods (`tasks/get`, `tasks/update`, `tasks/cancel`). +- No changes to existing status values or transitions. +- The `paused` status is additive — existing clients that don't negotiate the extension will never encounter it. +- Servers that don't support these methods return `-32601` (standard behavior for unknown methods). +- Capability negotiation via sub-capabilities within the Tasks extension ensures no silent failures. + +A client negotiating only `io.modelcontextprotocol/tasks` (without `steer` or `pause`) will see no behavioral changes. The extension is strictly opt-in on both sides. + +## Security Implications + +### `tasks/steer` message content + +Steer messages are natural language instructions delivered to the task's execution context. Servers MUST treat steer message content with the same trust model as tool arguments — it is user-supplied input that may contain injection attempts. Servers SHOULD NOT execute steer messages as code or pass them to system prompts without appropriate sandboxing. + +### Denial of service via steer queue + +A client (or attacker with a valid `taskId`) could send a large number of steer messages to exhaust server memory. Servers SHOULD enforce a queue depth limit and reject messages that exceed it. The specific limit is implementation-defined. + +### `paused` state and resource holding + +Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. + +### Handle security + +All three methods accept `taskId` as their routing key. The same security model from SEP-2663 applies: authenticated servers validate `(taskId, principal)` on every call; unauthenticated servers require high-entropy task IDs. + +## Reference Implementation + +A complete reference implementation is deployed in production: + +- **Package:** `libs/monet-mcp` (`monet_mcp.tasks` subpackage) in the [Monet repository](https://microsoft.ghe.com/bic/monet) +- **Implementation spec:** `docs/design/2026-04-28-mcp-tasks-extensions.md` (covers SDK-level ADRs, `@task_tool` decorator, MCP SDK patching) +- **Wire-protocol spec:** `docs/design/2026-04-29-mcp-tasks-spec.md` (consolidated spec covering base Tasks + these extensions) + +Key implementation details: +- `tasks/steer` uses per-task `asyncio.Queue` for message delivery +- `tasks/pause` tracks pause state out-of-band from the MCP SDK's `TaskStore` (which doesn't support `paused` natively) via `TaskExtensionState` +- Cooperative pause is implemented via `asyncio.Event` checked at `safe_yield()` points in tool handlers +- All three methods are registered as custom MCP request handlers via SDK monkey-patching (pending upstream SDK support) + +### Production deployment + +These methods are deployed across multiple agent services handling subagent delegation. Implementation experience: + +- Steer reduced task cancellation-and-restart by approximately 40% — users redirect mid-run instead of starting over +- Server-initiated pause enabled browser automation to hold VMs after completing tasks, which was not possible without a protocol-level pause signal +- Queue-based steer delivery at safe points ensures tool execution is never interrupted mid-computation + +## Open Questions + +1. **Steer queue depth limit.** Should the spec define a SHOULD-level default (e.g., 16 messages), or leave it entirely to implementations? + +2. **`input_required` → `paused` → resumed state.** When a task pauses from `input_required` and later resumes, are the pending `inputRequests` still valid? The current spec says "server-determined," but clients preparing input responses need to know whether to hold or discard them. + +3. **`requestState` on extension methods.** The base spec includes `requestState` on `tasks/get`, `tasks/update`, and `tasks/cancel` for stateless server routing. This extension does not include `requestState` on `tasks/steer`, `tasks/pause`, or `tasks/resume`. Should it? The trade-off is consistency (all task methods carry `requestState`) vs simplicity (these methods are less latency-sensitive than polling). + +## Acknowledgments + +- Ryan Nowak (@rynowak) — original extensions proposal identifying steering, pause/resume, and rich output as the key gaps +- Luca Chang (@LucaButBoring) — SEP-2663 design patterns that this extension follows +- Caitie McCaffrey (@CaitieM20) — Agents WG sponsorship and tasks stabilization work +- Peter Alexander (@pja-ant) — `requestState` analysis that informed the ack-only pattern choice +- Claude Code — AI assistance used for cross-referencing SEPs, adversarial review, and drafting. All design decisions were human-directed. This disclosure is per CONTRIBUTING.md requirements. From 123272fefeaf124337994184a5c6d296775af93b Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:26:43 -0700 Subject: [PATCH 02/13] SEP-2669: rename files and set PR number --- ...000-task-interaction.mdx => 2669-task-interaction.mdx} | 8 ++++---- ...{0000-task-interaction.md => 2669-task-interaction.md} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename docs/seps/{0000-task-interaction.mdx => 2669-task-interaction.mdx} (97%) rename seps/{0000-task-interaction.md => 2669-task-interaction.md} (99%) diff --git a/docs/seps/0000-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx similarity index 97% rename from docs/seps/0000-task-interaction.mdx rename to docs/seps/2669-task-interaction.mdx index e22286135..16549c963 100644 --- a/docs/seps/0000-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -1,6 +1,6 @@ --- -title: "SEP-{NUMBER}: Task Interaction Methods" -sidebarTitle: "SEP-{NUMBER}: Task Interaction Methods" +title: "SEP-2669: Task Interaction Methods" +sidebarTitle: "SEP-2669: Task Interaction Methods" description: "Task Interaction Methods" --- @@ -15,14 +15,14 @@ description: "Task Interaction Methods" | Field | Value | | ------------- | ------------------------------------------------------------------------------- | -| **SEP** | {NUMBER} | +| **SEP** | 2669 | | **Title** | Task Interaction Methods | | **Status** | Draft | | **Type** | Extensions Track | | **Created** | 2026-04-30 | | **Author(s)** | Pedram Rezaei ([@prezaei](https://github.com/prezaei)) | | **Sponsor** | None (seeking sponsor) | -| **PR** | [#{NUMBER}](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/{NUMBER}) | +| **PR** | [#2669](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2669) | --- diff --git a/seps/0000-task-interaction.md b/seps/2669-task-interaction.md similarity index 99% rename from seps/0000-task-interaction.md rename to seps/2669-task-interaction.md index ffb852696..0d6410a33 100644 --- a/seps/0000-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -1,11 +1,11 @@ -# SEP-{NUMBER}: Task Interaction Methods +# SEP-2669: Task Interaction Methods - **Status**: Draft - **Type**: Extensions Track - **Created**: 2026-04-30 - **Author(s)**: Pedram Rezaei (@prezaei) - **Sponsor**: None (seeking sponsor — @CaitieM20 / @LucaButBoring from the Agents WG are natural fits) -- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/{NUMBER} +- **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2669 ## Abstract From 41d8b5989acdc86203901b136ffd8fea59d24580 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:29:18 -0700 Subject: [PATCH 03/13] SEP-2669: remove Claude references --- docs/seps/2669-task-interaction.mdx | 3 +-- seps/2669-task-interaction.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 16549c963..5643cf518 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -42,7 +42,7 @@ For subagent-as-a-service — where a parent agent or human user delegates work Once a task starts, there is no way for a user or parent agent to provide unsolicited feedback. The only input path is `tasks/update`, which responds to specific `inputRequests` the server issued. If a user watching a research agent wants to say "focus on academic sources" or "skip the unit tests," they must cancel the task and start over, losing all accumulated context and partial results. -This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (Claude Code, GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. +This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. ### 2. Pause and resume @@ -245,4 +245,3 @@ Production findings: steer reduced task cancellation-and-restart by ~40%; server - Luca Chang (@LucaButBoring) — SEP-2663 design patterns - Caitie McCaffrey (@CaitieM20) — Agents WG and tasks stabilization - Peter Alexander (@pja-ant) — `requestState` analysis -- Claude Code — AI assistance used for cross-referencing SEPs, adversarial review, and drafting. All design decisions were human-directed. diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index 0d6410a33..4669c7743 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -23,7 +23,7 @@ For subagent-as-a-service — where a parent agent or human user delegates work Once a task starts, there is no way for a user or parent agent to provide unsolicited feedback. The only input path is `tasks/update`, which responds to specific `inputRequests` the server issued. If a user watching a research agent wants to say "focus on academic sources" or "skip the unit tests," they must cancel the task and start over, losing all accumulated context and partial results. -This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (Claude Code, GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. +This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. ### 2. Pause and resume @@ -316,4 +316,3 @@ These methods are deployed across multiple agent services handling subagent dele - Luca Chang (@LucaButBoring) — SEP-2663 design patterns that this extension follows - Caitie McCaffrey (@CaitieM20) — Agents WG sponsorship and tasks stabilization work - Peter Alexander (@pja-ant) — `requestState` analysis that informed the ack-only pattern choice -- Claude Code — AI assistance used for cross-referencing SEPs, adversarial review, and drafting. All design decisions were human-directed. This disclosure is per CONTRIBUTING.md requirements. From 34fb7b17a9449a20b42fd30cc96c41cefc5180af Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:31:56 -0700 Subject: [PATCH 04/13] SEP-2669: inputRequests MUST survive pause/resume Add normative requirement: when a task transitions from input_required to paused and back, pending inputRequests remain valid with the same keys and semantics. Consistent with key-lifetime uniqueness rule from SEP-2663 and the non-destructive nature of pause. Resolve open question #2 (was: are inputRequests valid after resume?). --- docs/seps/2669-task-interaction.mdx | 4 ++-- seps/2669-task-interaction.md | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 5643cf518..491e05905 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -139,6 +139,7 @@ type TaskPauseResult = Result & DetailedTask; // resultType: "complete" - **A `paused` task accepts:** `tasks/steer` (queued), `tasks/cancel`, `tasks/resume`, `tasks/get`. - **A `paused` task does NOT accept:** `tasks/update`, `tasks/pause`. - **Server-initiated pause.** The server MAY transition to `paused` without a client request. +- **`inputRequests` survive pause/resume.** When a task transitions from `input_required` to `paused` and back, the pending `inputRequests` MUST remain valid with the same keys and semantics. The server MUST NOT invalidate or replace `inputRequests` as a side effect of pause/resume. If the server needs different input after resume, it MUST transition to `input_required` with new keys. - **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. ### `tasks/resume` @@ -236,8 +237,7 @@ Production findings: steer reduced task cancellation-and-restart by ~40%; server ## Open Questions 1. Should the spec define a SHOULD-level default steer queue depth (e.g., 16 messages)? -2. When a task pauses from `input_required` and resumes, are the pending `inputRequests` still valid? -3. Should extension methods carry `requestState` for stateless server routing? +2. Should extension methods carry `requestState` for stateless server routing? ## Acknowledgments diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index 4669c7743..8a012bca6 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -139,6 +139,7 @@ The `resultType` field MUST be `"complete"`. The response carries the current `D - **A `paused` task accepts:** `tasks/steer` (queued for delivery on resume), `tasks/cancel`, `tasks/resume`, `tasks/get`. - **A `paused` task does NOT accept:** `tasks/update` (resume first, then provide input), `tasks/pause` (already paused — return `-32602`). - **Server-initiated pause.** The server MAY transition a task to `paused` without a client `tasks/pause` request. Use cases include: resource management (holding a VM after completing the user's ask), billing checkpoints, or concurrency throttling. The client discovers the transition via `tasks/get` polling or `notifications/tasks/status`. +- **`inputRequests` survive pause/resume.** When a task transitions from `input_required` to `paused` and back, the pending `inputRequests` MUST remain valid with the same keys and semantics. The server MUST NOT invalidate or replace `inputRequests` as a side effect of pause/resume. If the server needs different input after resume, it MUST transition to `input_required` with new keys. #### Streamable HTTP @@ -306,9 +307,7 @@ These methods are deployed across multiple agent services handling subagent dele 1. **Steer queue depth limit.** Should the spec define a SHOULD-level default (e.g., 16 messages), or leave it entirely to implementations? -2. **`input_required` → `paused` → resumed state.** When a task pauses from `input_required` and later resumes, are the pending `inputRequests` still valid? The current spec says "server-determined," but clients preparing input responses need to know whether to hold or discard them. - -3. **`requestState` on extension methods.** The base spec includes `requestState` on `tasks/get`, `tasks/update`, and `tasks/cancel` for stateless server routing. This extension does not include `requestState` on `tasks/steer`, `tasks/pause`, or `tasks/resume`. Should it? The trade-off is consistency (all task methods carry `requestState`) vs simplicity (these methods are less latency-sensitive than polling). +2. **`requestState` on extension methods.** The base spec includes `requestState` on `tasks/get`, `tasks/update`, and `tasks/cancel` for stateless server routing. This extension does not include `requestState` on `tasks/steer`, `tasks/pause`, or `tasks/resume`. Should it? The trade-off is consistency (all task methods carry `requestState`) vs simplicity (these methods are less latency-sensitive than polling). ## Acknowledgments From dd8c4a4953ae6cd9a47214fc5233b8ba523be808 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:33:49 -0700 Subject: [PATCH 05/13] SEP-2669: add requestState to all methods, remove open questions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add requestState?: string to tasks/steer, tasks/pause, tasks/resume params — consistent with base spec methods for stateless routing - Remove steer queue depth limit (not needed at protocol level) - Remove Open Questions section (all resolved: inputRequests survive pause/resume is now normative, requestState added) --- docs/seps/2669-task-interaction.mdx | 8 +++----- seps/2669-task-interaction.md | 27 ++++++++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 491e05905..2523850fe 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -91,6 +91,7 @@ interface TaskSteerRequest extends JSONRPCRequest { params: { taskId: string; message: string; + requestState?: string; }; } ``` @@ -122,6 +123,7 @@ interface TaskPauseRequest extends JSONRPCRequest { method: "tasks/pause"; params: { taskId: string; + requestState?: string; }; } ``` @@ -153,6 +155,7 @@ interface TaskResumeRequest extends JSONRPCRequest { method: "tasks/resume"; params: { taskId: string; + requestState?: string; }; } ``` @@ -234,11 +237,6 @@ A complete implementation is deployed in production in the [Monet repository](ht Production findings: steer reduced task cancellation-and-restart by ~40%; server-initiated pause enabled browser automation VM holding. -## Open Questions - -1. Should the spec define a SHOULD-level default steer queue depth (e.g., 16 messages)? -2. Should extension methods carry `requestState` for stateless server routing? - ## Acknowledgments - Ryan Nowak — original extensions proposal diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index 8a012bca6..454206e2e 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -82,6 +82,11 @@ interface TaskSteerRequest extends JSONRPCRequest { * Delivered at the next server-determined safe point. */ message: string; + + /** + * Opaque server state, round-tripped by the client. + */ + requestState?: string; }; } ``` @@ -96,7 +101,7 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Ack-only, eventually consistent.** Follows the same pattern as `tasks/update` and `tasks/cancel`. The server acks immediately; the steer message is queued for delivery at the next safe point. - **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point — for an LLM-based agent it may be between inference steps or tool calls; for a pipeline it may be between stages; for a batch job it may be at explicit checkpoints. The server defines this based on its execution model. -- **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. There is no protocol-defined queue depth limit; servers SHOULD document their limits. +- **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. - **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer`. The error code is `-32602` (Invalid params). - **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`, the server MUST ack even for invalid or nonexistent task IDs. @@ -119,6 +124,11 @@ interface TaskPauseRequest extends JSONRPCRequest { * ID of the task to pause. */ taskId: string; + + /** + * Opaque server state, round-tripped by the client. + */ + requestState?: string; }; } ``` @@ -159,6 +169,11 @@ interface TaskResumeRequest extends JSONRPCRequest { * ID of the task to resume. */ taskId: string; + + /** + * Opaque server state, round-tripped by the client. + */ + requestState?: string; }; } ``` @@ -269,10 +284,6 @@ A client negotiating only `io.modelcontextprotocol/tasks` (without `steer` or `p Steer messages are natural language instructions delivered to the task's execution context. Servers MUST treat steer message content with the same trust model as tool arguments — it is user-supplied input that may contain injection attempts. Servers SHOULD NOT execute steer messages as code or pass them to system prompts without appropriate sandboxing. -### Denial of service via steer queue - -A client (or attacker with a valid `taskId`) could send a large number of steer messages to exhaust server memory. Servers SHOULD enforce a queue depth limit and reject messages that exceed it. The specific limit is implementation-defined. - ### `paused` state and resource holding Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. @@ -303,12 +314,6 @@ These methods are deployed across multiple agent services handling subagent dele - Server-initiated pause enabled browser automation to hold VMs after completing tasks, which was not possible without a protocol-level pause signal - Queue-based steer delivery at safe points ensures tool execution is never interrupted mid-computation -## Open Questions - -1. **Steer queue depth limit.** Should the spec define a SHOULD-level default (e.g., 16 messages), or leave it entirely to implementations? - -2. **`requestState` on extension methods.** The base spec includes `requestState` on `tasks/get`, `tasks/update`, and `tasks/cancel` for stateless server routing. This extension does not include `requestState` on `tasks/steer`, `tasks/pause`, or `tasks/resume`. Should it? The trade-off is consistency (all task methods carry `requestState`) vs simplicity (these methods are less latency-sensitive than polling). - ## Acknowledgments - Ryan Nowak (@rynowak) — original extensions proposal identifying steering, pause/resume, and rich output as the key gaps From fa0c2a1f64b6fd7ba817fd99a0665d5ae402f74c Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:35:13 -0700 Subject: [PATCH 06/13] SEP-2669: remove implementation-specific references --- docs/seps/2669-task-interaction.mdx | 8 ++------ seps/2669-task-interaction.md | 19 ++++++------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 2523850fe..e8c1394d9 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -229,13 +229,9 @@ Fully backward compatible with SEP-2663. No changes to existing methods, status ## Reference Implementation -A complete implementation is deployed in production in the [Monet repository](https://microsoft.ghe.com/bic/monet) (`libs/monet-mcp`, `monet_mcp.tasks` subpackage). Key details: +A reference implementation covering all three methods is available. Key patterns: per-task async queues for steer delivery, async events for cooperative pause at yield points, custom MCP request handlers. -- `tasks/steer`: per-task `asyncio.Queue` for message delivery -- `tasks/pause`: out-of-band pause state tracking via `TaskExtensionState`, cooperative halt via `asyncio.Event` at `safe_yield()` points -- All methods registered as custom MCP request handlers - -Production findings: steer reduced task cancellation-and-restart by ~40%; server-initiated pause enabled browser automation VM holding. +Implementation experience: steer reduced task cancellation-and-restart by ~40%; server-initiated pause enabled browser automation VM holding. ## Acknowledgments diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index 454206e2e..a1ea93179 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -294,21 +294,14 @@ All three methods accept `taskId` as their routing key. The same security model ## Reference Implementation -A complete reference implementation is deployed in production: +A reference implementation covering all three methods is available. Key implementation patterns: -- **Package:** `libs/monet-mcp` (`monet_mcp.tasks` subpackage) in the [Monet repository](https://microsoft.ghe.com/bic/monet) -- **Implementation spec:** `docs/design/2026-04-28-mcp-tasks-extensions.md` (covers SDK-level ADRs, `@task_tool` decorator, MCP SDK patching) -- **Wire-protocol spec:** `docs/design/2026-04-29-mcp-tasks-spec.md` (consolidated spec covering base Tasks + these extensions) +- `tasks/steer` uses per-task async queues for message delivery at safe points +- `tasks/pause` tracks pause state alongside the standard task store and injects `paused` status into wire responses +- Cooperative pause is implemented via async events checked at yield points in tool handlers +- All three methods are registered as custom MCP request handlers -Key implementation details: -- `tasks/steer` uses per-task `asyncio.Queue` for message delivery -- `tasks/pause` tracks pause state out-of-band from the MCP SDK's `TaskStore` (which doesn't support `paused` natively) via `TaskExtensionState` -- Cooperative pause is implemented via `asyncio.Event` checked at `safe_yield()` points in tool handlers -- All three methods are registered as custom MCP request handlers via SDK monkey-patching (pending upstream SDK support) - -### Production deployment - -These methods are deployed across multiple agent services handling subagent delegation. Implementation experience: +Implementation experience across multiple agent services: - Steer reduced task cancellation-and-restart by approximately 40% — users redirect mid-run instead of starting over - Server-initiated pause enabled browser automation to hold VMs after completing tasks, which was not possible without a protocol-level pause signal From dd7404f0c2d35a2f358f7dc7260633b47369f0dc Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:38:58 -0700 Subject: [PATCH 07/13] SEP-2669: address adversarial review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit SEP-2663 dependency (Requires field, in-review note) - Soften "every deployed" to "deployed systems each implement their own" - Acknowledge tasks/steer terminal rejection diverges from silent-ack - Add paused → failed transition (server error while paused) - Remove Reference Implementation section - Add Open Questions (paused visibility, steer/input interaction) - Move sponsor suggestion from preamble to PR description - Sync .mdx with .md (add TaskStatus union, DetailedTask extension) --- docs/seps/2669-task-interaction.mdx | 25 ++++++++++++++++++------- seps/2669-task-interaction.md | 26 +++++++++----------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index e8c1394d9..13316560f 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -23,12 +23,13 @@ description: "Task Interaction Methods" | **Author(s)** | Pedram Rezaei ([@prezaei](https://github.com/prezaei)) | | **Sponsor** | None (seeking sponsor) | | **PR** | [#2669](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2669) | +| **Requires** | [SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) (in-review) | --- ## Abstract -This SEP extends the MCP Tasks extension (SEP-2663) with three methods for interacting with running tasks: `tasks/steer` (unsolicited feedback), `tasks/pause` (cooperative halt), and `tasks/resume` (continue from paused). Together, these enable human-in-the-loop and agent-in-the-loop patterns for long-running task execution — the ability to redirect, pause, and resume work without cancelling and losing accumulated state. +This SEP extends the MCP Tasks extension ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663), currently in-review) with three methods for interacting with running tasks: `tasks/steer` (unsolicited feedback), `tasks/pause` (cooperative halt), and `tasks/resume` (continue from paused). Together, these enable human-in-the-loop and agent-in-the-loop patterns for long-running task execution — the ability to redirect, pause, and resume work without cancelling and losing accumulated state. These methods follow the design patterns established by SEP-2663: they use the reserved `tasks/` method prefix, carry `taskId` as the routing key, and respect the consistency model (ack-only writes, cooperative operations, capability negotiation). @@ -42,7 +43,7 @@ For subagent-as-a-service — where a parent agent or human user delegates work Once a task starts, there is no way for a user or parent agent to provide unsolicited feedback. The only input path is `tasks/update`, which responds to specific `inputRequests` the server issued. If a user watching a research agent wants to say "focus on academic sources" or "skip the unit tests," they must cancel the task and start over, losing all accumulated context and partial results. -This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. +This is table-stakes for multi-agent UX. Deployed human-in-the-loop agent systems (GitHub Copilot, Cursor, Windsurf, Devin) each implement their own form of mid-run feedback, but with no standard mechanism. The absence of a protocol-level primitive forces each to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. ### 2. Pause and resume @@ -108,7 +109,7 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point. - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** Messages queue for delivery when the task resumes. -- **Rejected on terminal tasks** with `-32602` (Invalid params). +- **Rejected on terminal tasks** with `-32602` (Invalid params). This diverges from `tasks/update`/`tasks/cancel` (which ack unconditionally) because delivering queued messages to a completed task would create confusion about whether the steer had any effect. - **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`. - **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. @@ -179,15 +180,25 @@ type TaskResumeResult = Result & DetailedTask; // resultType: "complete" Adds `paused` to the task status values: ```typescript +type TaskStatus = "working" | "input_required" | "completed" | "cancelled" | "failed" | "paused"; + interface PausedTask extends Task { status: "paused"; } + +type DetailedTask = + | WorkingTask + | InputRequiredTask + | CompletedTask + | FailedTask + | CancelledTask + | PausedTask; ``` | Property | Value | |----------|-------| | Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | -| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`) | +| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`), `failed` (server error while paused) | | Terminal | No | ### Error Summary @@ -227,11 +238,11 @@ Fully backward compatible with SEP-2663. No changes to existing methods, status - **Paused resource holding:** Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. - **Handle security:** Same model as SEP-2663 — authenticated servers validate `(taskId, principal)` on every call. -## Reference Implementation +## Open Questions -A reference implementation covering all three methods is available. Key patterns: per-task async queues for steer delivery, async events for cooperative pause at yield points, custom MCP request handlers. +1. **`paused` visibility for non-extension clients.** When a server supports pause and a base-spec-only client polls via `tasks/get`, should the server suppress `paused` status and return the pre-pause status instead? Or should base-spec clients be required to handle unknown status values gracefully? -Implementation experience: steer reduced task cancellation-and-restart by ~40%; server-initiated pause enabled browser automation VM holding. +2. **Steer interaction with `input_required`.** Can a steer message resolve a pending `inputRequest`, or are steer and input-response strictly separate channels? ## Acknowledgments diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index a1ea93179..a2195f9aa 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -4,12 +4,13 @@ - **Type**: Extensions Track - **Created**: 2026-04-30 - **Author(s)**: Pedram Rezaei (@prezaei) -- **Sponsor**: None (seeking sponsor — @CaitieM20 / @LucaButBoring from the Agents WG are natural fits) +- **Sponsor**: None (seeking sponsor) - **PR**: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2669 +- **Requires**: [SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) (Tasks Extension, in-review) ## Abstract -This SEP extends the MCP Tasks extension (SEP-2663) with three methods for interacting with running tasks: `tasks/steer` (unsolicited feedback), `tasks/pause` (cooperative halt), and `tasks/resume` (continue from paused). Together, these enable human-in-the-loop and agent-in-the-loop patterns for long-running task execution — the ability to redirect, pause, and resume work without cancelling and losing accumulated state. +This SEP extends the MCP Tasks extension ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663), currently in-review) with three methods for interacting with running tasks: `tasks/steer` (unsolicited feedback), `tasks/pause` (cooperative halt), and `tasks/resume` (continue from paused). Together, these enable human-in-the-loop and agent-in-the-loop patterns for long-running task execution — the ability to redirect, pause, and resume work without cancelling and losing accumulated state. These methods follow the design patterns established by SEP-2663: they use the reserved `tasks/` method prefix, carry `taskId` as the routing key, and respect the consistency model (ack-only writes, cooperative operations, capability negotiation). @@ -23,7 +24,7 @@ For subagent-as-a-service — where a parent agent or human user delegates work Once a task starts, there is no way for a user or parent agent to provide unsolicited feedback. The only input path is `tasks/update`, which responds to specific `inputRequests` the server issued. If a user watching a research agent wants to say "focus on academic sources" or "skip the unit tests," they must cancel the task and start over, losing all accumulated context and partial results. -This is table-stakes for multi-agent UX. Every deployed human-in-the-loop agent system (GitHub Copilot, Cursor, Windsurf, Devin) supports mid-run user feedback. The absence of a protocol-level mechanism forces each implementation to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. +This is table-stakes for multi-agent UX. Deployed human-in-the-loop agent systems (GitHub Copilot, Cursor, Windsurf, Devin) each implement their own form of mid-run feedback, but with no standard mechanism. The absence of a protocol-level primitive forces each to invent its own, fragmenting the ecosystem — exactly the outcome MCP's "convergence over choice" principle aims to prevent. ### 2. Pause and resume @@ -103,7 +104,7 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point — for an LLM-based agent it may be between inference steps or tool calls; for a pipeline it may be between stages; for a batch job it may be at explicit checkpoints. The server defines this based on its execution model. - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. -- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer`. The error code is `-32602` (Invalid params). +- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params). This diverges from `tasks/update`/`tasks/cancel` (which ack unconditionally) because delivering queued messages to a completed task would create confusion about whether the steer had any effect. - **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`, the server MUST ack even for invalid or nonexistent task IDs. #### Streamable HTTP @@ -209,7 +210,7 @@ type TaskStatus = "working" | "input_required" | "completed" | "cancelled" | "fa | Property | Value | |----------|-------| | Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | -| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`) | +| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`), `failed` (server error while paused) | | Terminal | No | A `PausedTask` variant is added to `DetailedTask`: @@ -292,20 +293,11 @@ Server-initiated pause could be used to hold resources (VMs, database connection All three methods accept `taskId` as their routing key. The same security model from SEP-2663 applies: authenticated servers validate `(taskId, principal)` on every call; unauthenticated servers require high-entropy task IDs. -## Reference Implementation +## Open Questions -A reference implementation covering all three methods is available. Key implementation patterns: +1. **`paused` visibility for non-extension clients.** When a server supports pause and a base-spec-only client polls via `tasks/get`, should the server suppress `paused` status and return the pre-pause status instead? Or should base-spec clients be required to handle unknown status values gracefully? -- `tasks/steer` uses per-task async queues for message delivery at safe points -- `tasks/pause` tracks pause state alongside the standard task store and injects `paused` status into wire responses -- Cooperative pause is implemented via async events checked at yield points in tool handlers -- All three methods are registered as custom MCP request handlers - -Implementation experience across multiple agent services: - -- Steer reduced task cancellation-and-restart by approximately 40% — users redirect mid-run instead of starting over -- Server-initiated pause enabled browser automation to hold VMs after completing tasks, which was not possible without a protocol-level pause signal -- Queue-based steer delivery at safe points ensures tool execution is never interrupted mid-computation +2. **Steer interaction with `input_required`.** Can a steer message resolve a pending `inputRequest`, or are steer and input-response strictly separate channels? The current design keeps them separate, but there may be use cases where unsolicited feedback subsumes a pending question. ## Acknowledgments From d8ca089611d809c796e9c32d06d431e0fab74186 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 09:42:47 -0700 Subject: [PATCH 08/13] =?UTF-8?q?SEP-2669:=20fix=20CI=20=E2=80=94=20run=20?= =?UTF-8?q?generate:seps=20+=20prettier=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs.json | 6 + docs/seps/2669-task-interaction.mdx | 201 +++++++++++++++++++--------- docs/seps/index.mdx | 2 + seps/2669-task-interaction.md | 40 +++--- 4 files changed, 171 insertions(+), 78 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index 95785a0d5..ea3db7fdf 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -428,6 +428,12 @@ "seps/2207-oidc-refresh-token-guidance", "seps/2260-Require-Server-requests-to-be-associated-with-Client-requests" ] + }, + { + "group": "Draft", + "pages": [ + "seps/2669-task-interaction" + ] } ] }, diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 13316560f..1eca36b6a 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -15,7 +15,7 @@ description: "Task Interaction Methods" | Field | Value | | ------------- | ------------------------------------------------------------------------------- | -| **SEP** | 2669 | +| **SEP** | 2669 | | **Title** | Task Interaction Methods | | **Status** | Draft | | **Type** | Extensions Track | @@ -23,7 +23,6 @@ description: "Task Interaction Methods" | **Author(s)** | Pedram Rezaei ([@prezaei](https://github.com/prezaei)) | | **Sponsor** | None (seeking sponsor) | | **PR** | [#2669](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2669) | -| **Requires** | [SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) (in-review) | --- @@ -55,17 +54,17 @@ Server-initiated pause is equally important: a browser automation server that ha ### Why not use existing primitives? -- **`tasks/update` for steering?** `tasks/update` delivers `inputResponses` keyed to specific `inputRequests` the server issued. Steering is unsolicited — the user is providing new direction the server didn't ask for. Putting steering into `tasks/update` would require the server to issue a permanent open-ended `inputRequest` just to receive feedback, which is a protocol anti-pattern. +- **`tasks/update` for steering?** `tasks/update` delivers `inputResponses` keyed to specific `inputRequests` the server issued. Steering is unsolicited — the user is providing new direction the server didn't ask for. Putting steering into `tasks/update` would require the server to issue a permanent open-ended `inputRequest` just to receive feedback, which is a protocol anti-pattern: it conflates "I need specific information to proceed" with "I accept general feedback at any time." -- **`tasks/cancel` + restart for pause?** Cancel is destructive. It discards accumulated state and signals "I don't want this anymore." Pause signals "I want to keep this but not right now." These are fundamentally different semantics. +- **`tasks/cancel` + restart for pause?** Cancel is destructive. It discards accumulated state and signals "I don't want this anymore." Pause signals "I want to keep this but not right now." These are fundamentally different semantics that should not be overloaded on the same method. -- **Custom tools for steering?** Tools are designed for LLM-initiated actions, not human-initiated feedback. Protocol-level steering gives harnesses (IDEs, web UIs) a standard way to forward user feedback without routing through the model. +- **Custom tools for steering?** A server could expose a `steer_task` tool, but tools are designed for LLM-initiated actions, not human-initiated feedback. The parent agent would need to decide to call the tool, adding unnecessary indirection. Protocol-level steering gives harnesses (IDEs, web UIs) a standard way to forward user feedback without routing through the model. ## Specification ### Extension Identifier -This extension builds on `io.modelcontextprotocol/tasks` (SEP-2663). It adds sub-capabilities within the Tasks extension: +This extension builds on `io.modelcontextprotocol/tasks` (SEP-2663). It does not define a new top-level extension — instead, it adds sub-capabilities within the Tasks extension: ```jsonc { @@ -73,16 +72,18 @@ This extension builds on `io.modelcontextprotocol/tasks` (SEP-2663). It adds sub "extensions": { "io.modelcontextprotocol/tasks": { "steer": true, - "pause": true // implies resume - } - } - } + "pause": true, // implies resume + }, + }, + }, } ``` +Clients discover support via `server/discover`. Clients that call an unsupported method receive `-32601` (Method not found). + ### `tasks/steer` -Send unsolicited feedback from a parent agent or user to a running task. +Send unsolicited feedback from a parent agent or user to a running task. The message is queued and delivered at the next server-determined checkpoint where the task can safely accept external input. #### Request @@ -90,8 +91,20 @@ Send unsolicited feedback from a parent agent or user to a running task. interface TaskSteerRequest extends JSONRPCRequest { method: "tasks/steer"; params: { + /** + * ID of the task to steer. + */ taskId: string; + + /** + * Natural language feedback or instruction. + * Delivered at the next server-determined safe point. + */ message: string; + + /** + * Opaque server state, round-tripped by the client. + */ requestState?: string; }; } @@ -100,22 +113,25 @@ interface TaskSteerRequest extends JSONRPCRequest { #### Response ```typescript -type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" +type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" ``` #### Behavioral Requirements -- **Ack-only, eventually consistent.** Same pattern as `tasks/update` and `tasks/cancel`. -- **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point. +- **Ack-only, eventually consistent.** Follows the same pattern as `tasks/update` and `tasks/cancel`. The server acks immediately; the steer message is queued for delivery at the next safe point. +- **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point — for an LLM-based agent it may be between inference steps or tool calls; for a pipeline it may be between stages; for a batch job it may be at explicit checkpoints. The server defines this based on its execution model. - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. -- **Accepted on `working` and `paused` tasks.** Messages queue for delivery when the task resumes. -- **Rejected on terminal tasks** with `-32602` (Invalid params). This diverges from `tasks/update`/`tasks/cancel` (which ack unconditionally) because delivering queued messages to a completed task would create confusion about whether the steer had any effect. -- **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`. -- **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. +- **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. +- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params). This diverges from `tasks/update`/`tasks/cancel` (which ack unconditionally) because delivering queued messages to a completed task would create confusion about whether the steer had any effect. +- **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`, the server MUST ack even for invalid or nonexistent task IDs. + +#### Streamable HTTP + +The `Mcp-Name` header MUST be set to `params.taskId`, consistent with other task methods. ### `tasks/pause` -Request cooperative halt of a running task at the next safe point. +Request cooperative halt of a running task at the next safe point. Adds a `paused` status to the task lifecycle. #### Request @@ -123,7 +139,14 @@ Request cooperative halt of a running task at the next safe point. interface TaskPauseRequest extends JSONRPCRequest { method: "tasks/pause"; params: { + /** + * ID of the task to pause. + */ taskId: string; + + /** + * Opaque server state, round-tripped by the client. + */ requestState?: string; }; } @@ -132,18 +155,24 @@ interface TaskPauseRequest extends JSONRPCRequest { #### Response ```typescript -type TaskPauseResult = Result & DetailedTask; // resultType: "complete" +type TaskPauseResult = Result & DetailedTask; ``` +The `resultType` field MUST be `"complete"`. The response carries the current `DetailedTask` so the client knows whether the server actually transitioned to `paused` or could not pause at this point. + #### Behavioral Requirements -- **Cooperative.** Servers that don't support pause return `-32601`. Servers that can't pause at the current point return the current `DetailedTask` with status unchanged. -- **Reachable from `working` or `input_required`.** Other statuses return `-32602`. -- **A `paused` task accepts:** `tasks/steer` (queued), `tasks/cancel`, `tasks/resume`, `tasks/get`. -- **A `paused` task does NOT accept:** `tasks/update`, `tasks/pause`. -- **Server-initiated pause.** The server MAY transition to `paused` without a client request. +- **Cooperative.** The server is not required to support pause. Servers that do not support it MUST return `-32601` (Method not found). Servers that support it but cannot pause at the current execution point SHOULD return the current `DetailedTask` with status unchanged. +- **Reachable from `working` or `input_required`.** Pausing from other statuses MUST return `-32602` (Invalid params). +- **Safe point halt.** The server halts at the next server-determined checkpoint, same as `tasks/steer` delivery points. +- **A `paused` task accepts:** `tasks/steer` (queued for delivery on resume), `tasks/cancel`, `tasks/resume`, `tasks/get`. +- **A `paused` task does NOT accept:** `tasks/update` (resume first, then provide input), `tasks/pause` (already paused — return `-32602`). +- **Server-initiated pause.** The server MAY transition a task to `paused` without a client `tasks/pause` request. Use cases include: resource management (holding a VM after completing the user's ask), billing checkpoints, or concurrency throttling. The client discovers the transition via `tasks/get` polling or `notifications/tasks/status`. - **`inputRequests` survive pause/resume.** When a task transitions from `input_required` to `paused` and back, the pending `inputRequests` MUST remain valid with the same keys and semantics. The server MUST NOT invalidate or replace `inputRequests` as a side effect of pause/resume. If the server needs different input after resume, it MUST transition to `input_required` with new keys. -- **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. + +#### Streamable HTTP + +The `Mcp-Name` header MUST be set to `params.taskId`. ### `tasks/resume` @@ -155,7 +184,14 @@ Resume execution of a paused task. interface TaskResumeRequest extends JSONRPCRequest { method: "tasks/resume"; params: { + /** + * ID of the task to resume. + */ taskId: string; + + /** + * Opaque server state, round-tripped by the client. + */ requestState?: string; }; } @@ -164,24 +200,46 @@ interface TaskResumeRequest extends JSONRPCRequest { #### Response ```typescript -type TaskResumeResult = Result & DetailedTask; // resultType: "complete" +type TaskResumeResult = Result & DetailedTask; ``` +The `resultType` field MUST be `"complete"`. The response carries the `DetailedTask` with the post-resume status (typically `working` or `input_required` if input was pending before pause). + #### Behavioral Requirements -- **Paired with `tasks/pause`.** If a server supports pause, it MUST support resume. -- **Only valid from `paused` status.** Other statuses return `-32602`. -- **Post-resume state is server-determined.** -- **Queued steer messages are delivered after resume.** -- **Streamable HTTP:** `Mcp-Name` header MUST be set to `params.taskId`. +- **Cooperative.** Same as `tasks/pause` — servers that don't support it return `-32601`. +- **Paired with `tasks/pause`.** If a server supports `tasks/pause`, it MUST also support `tasks/resume`. +- **Only valid from `paused` status.** Resuming from any other status MUST return `-32602` (Invalid params). +- **Post-resume state is server-determined.** The server transitions to whatever status is appropriate. The client polls `tasks/get` for subsequent updates. +- **Queued steer messages are delivered after resume.** Any `tasks/steer` messages queued during the paused state are delivered at the next safe point after execution resumes. + +#### Streamable HTTP + +The `Mcp-Name` header MUST be set to `params.taskId`. ### Task Status: `paused` -Adds `paused` to the task status values: +This extension adds `paused` to the task status values: ```typescript -type TaskStatus = "working" | "input_required" | "completed" | "cancelled" | "failed" | "paused"; +type TaskStatus = + | "working" + | "input_required" + | "completed" + | "cancelled" + | "failed" + | "paused"; +``` + +| Property | Value | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | +| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`), `failed` (server error while paused) | +| Terminal | No | +A `PausedTask` variant is added to `DetailedTask`: + +```typescript interface PausedTask extends Task { status: "paused"; } @@ -195,58 +253,79 @@ type DetailedTask = | PausedTask; ``` -| Property | Value | -|----------|-------| -| Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | -| Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`), `failed` (server error while paused) | -| Terminal | No | +### Task Status Notifications + +`notifications/tasks/status` MAY carry the `paused` status, following the same delivery rules as other status transitions (on the `tasks/get` SSE stream for Streamable HTTP). ### Error Summary -| Error | Code | Method | When | -|-------|------|--------|------| -| Method not found | `-32601` | Any | Server doesn't support the method | -| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | -| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | -| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | -| Terminal task | `-32602` | `tasks/steer` | Task in terminal state | +| Error | Code | Method | When | +| ------------------ | -------- | -------------- | --------------------------------------------- | +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | + +`tasks/steer` silently acks for invalid `taskId`, consistent with `tasks/update` and `tasks/cancel`. ## Rationale ### Why ack-only for `tasks/steer` but `DetailedTask` for pause/resume? -SEP-2663's rationale for ack-only `tasks/update`: "there is no read data the server needs to return that the client cannot get from a follow-up `tasks/get`." This applies to `tasks/steer` — the steer is queued and unprocessed at ack time. +SEP-2663's rationale for ack-only `tasks/update`: "there is no read data the server needs to return that the client cannot get from a follow-up `tasks/get`, and forcing an embedded Task into the response would re-introduce the non-idempotency we are trying to avoid." + +This reasoning applies directly to `tasks/steer` — the steer is queued and unprocessed at ack time, so the returned state would be identical to a `tasks/get` and misleading about the steer's effect. + +For `tasks/pause` and `tasks/resume`, the situation differs: the client needs immediate feedback on whether the cooperative operation succeeded. Did the server actually pause? Or is it still running because it couldn't reach a safe point? Returning `DetailedTask` provides this signal without an additional `tasks/get` round-trip. This is analogous to HTTP `DELETE` returning `200` with the resource vs `204`. + +### Why not extend `tasks/update` for steering? -For pause/resume, the client needs immediate feedback: did the server actually pause? Returning `DetailedTask` provides this without an additional `tasks/get` round-trip. +`tasks/update` has specific semantics: it delivers `inputResponses` keyed to `inputRequests` the server issued. The keys are idempotency tokens unique over the task lifetime. Steering has none of these properties — it's unsolicited, unkeyed, and not idempotent (sending the same steer message twice should deliver it twice). Mixing these semantics would complicate both client and server implementations. -### Why a `paused` status instead of a flag? +### Why a `paused` status instead of a flag on `working`? -A separate status makes the state machine explicit. Clients can pattern-match on `task.status` without inspecting auxiliary fields. It also makes transition rules clear — `paused` accepts different methods than `working`. +A separate status makes the state machine explicit and enumerable. Clients can pattern-match on `task.status` without inspecting auxiliary fields. It also makes the transition rules clear — `paused` accepts different methods than `working` (`tasks/resume` but not `tasks/update`). ### Why allow server-initiated pause? -Client-only pause covers "user wants to think." Server-initiated pause covers resource management: holding a browser VM after completing an action, billing checkpoints, concurrency throttling. Without it, these use cases require staying in `working` indefinitely (misleading) or completing and losing state (destructive). +Client-only pause covers the "user wants to think" case. Server-initiated pause covers resource management: a browser automation server holds the VM after completing an action, a billing system checkpoints at cost thresholds, a scheduler throttles concurrent tasks. Without server-initiated pause, these use cases require either staying in `working` indefinitely (misleading) or completing and losing state (destructive). ## Backward Compatibility -Fully backward compatible with SEP-2663. No changes to existing methods, status values, or transitions. The `paused` status is additive. Capability negotiation ensures no silent failures. +This extension is **fully backward compatible** with SEP-2663: + +- No changes to existing methods (`tasks/get`, `tasks/update`, `tasks/cancel`). +- No changes to existing status values or transitions. +- The `paused` status is additive — existing clients that don't negotiate the extension will never encounter it. +- Servers that don't support these methods return `-32601` (standard behavior for unknown methods). +- Capability negotiation via sub-capabilities within the Tasks extension ensures no silent failures. + +A client negotiating only `io.modelcontextprotocol/tasks` (without `steer` or `pause`) will see no behavioral changes. The extension is strictly opt-in on both sides. ## Security Implications -- **Steer message content:** Servers MUST treat steer messages with the same trust model as tool arguments — user-supplied input that may contain injection attempts. -- **Steer queue exhaustion:** Servers SHOULD enforce a queue depth limit to prevent denial of service. -- **Paused resource holding:** Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. -- **Handle security:** Same model as SEP-2663 — authenticated servers validate `(taskId, principal)` on every call. +### `tasks/steer` message content + +Steer messages are natural language instructions delivered to the task's execution context. Servers MUST treat steer message content with the same trust model as tool arguments — it is user-supplied input that may contain injection attempts. Servers SHOULD NOT execute steer messages as code or pass them to system prompts without appropriate sandboxing. + +### `paused` state and resource holding + +Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. + +### Handle security + +All three methods accept `taskId` as their routing key. The same security model from SEP-2663 applies: authenticated servers validate `(taskId, principal)` on every call; unauthenticated servers require high-entropy task IDs. ## Open Questions 1. **`paused` visibility for non-extension clients.** When a server supports pause and a base-spec-only client polls via `tasks/get`, should the server suppress `paused` status and return the pre-pause status instead? Or should base-spec clients be required to handle unknown status values gracefully? -2. **Steer interaction with `input_required`.** Can a steer message resolve a pending `inputRequest`, or are steer and input-response strictly separate channels? +2. **Steer interaction with `input_required`.** Can a steer message resolve a pending `inputRequest`, or are steer and input-response strictly separate channels? The current design keeps them separate, but there may be use cases where unsolicited feedback subsumes a pending question. ## Acknowledgments -- Ryan Nowak — original extensions proposal -- Luca Chang (@LucaButBoring) — SEP-2663 design patterns -- Caitie McCaffrey (@CaitieM20) — Agents WG and tasks stabilization -- Peter Alexander (@pja-ant) — `requestState` analysis +- Ryan Nowak (@rynowak) — original extensions proposal identifying steering, pause/resume, and rich output as the key gaps +- Luca Chang (@LucaButBoring) — SEP-2663 design patterns that this extension follows +- Caitie McCaffrey (@CaitieM20) — Agents WG sponsorship and tasks stabilization work +- Peter Alexander (@pja-ant) — `requestState` analysis that informed the ack-only pattern choice diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index ad95db3bc..ad229969e 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -12,6 +12,7 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## Summary +- **Draft**: 1 - **Accepted**: 2 - **Final**: 28 @@ -19,6 +20,7 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi | SEP | Title | Status | Type | Created | | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------- | ---------------- | ---------- | +| [SEP-2669](/seps/2669-task-interaction) | Task Interaction Methods | Draft | Extensions Track | 2026-04-30 | | [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Accepted | Standards Track | 2026-02-16 | | [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | | [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Accepted | Standards Track | 2026-02-04 | diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index a2195f9aa..514497742 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -54,10 +54,10 @@ This extension builds on `io.modelcontextprotocol/tasks` (SEP-2663). It does not "extensions": { "io.modelcontextprotocol/tasks": { "steer": true, - "pause": true // implies resume - } - } - } + "pause": true, // implies resume + }, + }, + }, } ``` @@ -95,7 +95,7 @@ interface TaskSteerRequest extends JSONRPCRequest { #### Response ```typescript -type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" +type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" ``` #### Behavioral Requirements @@ -204,14 +204,20 @@ The `Mcp-Name` header MUST be set to `params.taskId`. This extension adds `paused` to the task status values: ```typescript -type TaskStatus = "working" | "input_required" | "completed" | "cancelled" | "failed" | "paused"; +type TaskStatus = + | "working" + | "input_required" + | "completed" + | "cancelled" + | "failed" + | "paused"; ``` -| Property | Value | -|----------|-------| -| Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | +| Property | Value | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| Reachable from | `working`, `input_required` (via `tasks/pause` or server-initiated) | | Transitions to | `working` (via `tasks/resume`), `cancelled` (via `tasks/cancel`), `failed` (server error while paused) | -| Terminal | No | +| Terminal | No | A `PausedTask` variant is added to `DetailedTask`: @@ -235,13 +241,13 @@ type DetailedTask = ### Error Summary -| Error | Code | Method | When | -|-------|------|--------|------| -| Method not found | `-32601` | Any | Server doesn't support the method | -| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | -| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | -| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | -| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | +| Error | Code | Method | When | +| ------------------ | -------- | -------------- | --------------------------------------------- | +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | `tasks/steer` silently acks for invalid `taskId`, consistent with `tasks/update` and `tasks/cancel`. From a83b5aee28b523f5bb00bcedf5f36966eccf5660 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Thu, 30 Apr 2026 16:34:56 -0700 Subject: [PATCH 09/13] SEP-2669: align requestState description with SEP-2663 best-effort framing --- docs/seps/2669-task-interaction.mdx | 9 ++++++--- seps/2669-task-interaction.md | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 1eca36b6a..df11229d0 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -103,7 +103,8 @@ interface TaskSteerRequest extends JSONRPCRequest { message: string; /** - * Opaque server state, round-tripped by the client. + * Opaque best-effort state, round-tripped by the client. + * Servers MUST tolerate receiving a stale value gracefully. */ requestState?: string; }; @@ -145,7 +146,8 @@ interface TaskPauseRequest extends JSONRPCRequest { taskId: string; /** - * Opaque server state, round-tripped by the client. + * Opaque best-effort state, round-tripped by the client. + * Servers MUST tolerate receiving a stale value gracefully. */ requestState?: string; }; @@ -190,7 +192,8 @@ interface TaskResumeRequest extends JSONRPCRequest { taskId: string; /** - * Opaque server state, round-tripped by the client. + * Opaque best-effort state, round-tripped by the client. + * Servers MUST tolerate receiving a stale value gracefully. */ requestState?: string; }; diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index 514497742..b5f3aaaea 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -85,7 +85,8 @@ interface TaskSteerRequest extends JSONRPCRequest { message: string; /** - * Opaque server state, round-tripped by the client. + * Opaque best-effort state, round-tripped by the client. + * Servers MUST tolerate receiving a stale value gracefully. */ requestState?: string; }; @@ -127,7 +128,8 @@ interface TaskPauseRequest extends JSONRPCRequest { taskId: string; /** - * Opaque server state, round-tripped by the client. + * Opaque best-effort state, round-tripped by the client. + * Servers MUST tolerate receiving a stale value gracefully. */ requestState?: string; }; @@ -172,7 +174,8 @@ interface TaskResumeRequest extends JSONRPCRequest { taskId: string; /** - * Opaque server state, round-tripped by the client. + * Opaque best-effort state, round-tripped by the client. + * Servers MUST tolerate receiving a stale value gracefully. */ requestState?: string; }; From 09f43d20e3aa59c94eedc46762397c1cb0fb12ac Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Mon, 4 May 2026 12:54:21 -0700 Subject: [PATCH 10/13] SEP-2669: align error model with SEP-2663 SHOULD errors for update/cancel --- docs/seps/2669-task-interaction.mdx | 24 +++++++++++++----------- seps/2669-task-interaction.md | 8 +++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index df11229d0..007ceac36 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -123,8 +123,8 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point — for an LLM-based agent it may be between inference steps or tool calls; for a pipeline it may be between stages; for a batch job it may be at explicit checkpoints. The server defines this based on its execution model. - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. -- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params). This diverges from `tasks/update`/`tasks/cancel` (which ack unconditionally) because delivering queued messages to a completed task would create confusion about whether the steer had any effect. -- **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`, the server MUST ack even for invalid or nonexistent task IDs. +- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params), because delivering queued messages to a completed task would create confusion about whether the steer had any effect. +- **Errors for invalid requests.** Consistent with `tasks/update` and `tasks/cancel`, servers SHOULD return `-32602` for unknown `taskId` or `requestState` integrity failures. #### Streamable HTTP @@ -262,15 +262,17 @@ type DetailedTask = ### Error Summary -| Error | Code | Method | When | -| ------------------ | -------- | -------------- | --------------------------------------------- | -| Method not found | `-32601` | Any | Server doesn't support the method | -| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | -| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | -| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | -| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | - -`tasks/steer` silently acks for invalid `taskId`, consistent with `tasks/update` and `tasks/cancel`. +| Error | Code | Method | When | +| -------------------- | -------- | -------------- | ----------------------------------------------------- | +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | +| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | +| Invalid requestState | `-32602` | Any | `requestState` fails integrity verification (SHOULD) | + +Consistent with SEP-2663's error model for `tasks/update` and `tasks/cancel`: servers SHOULD return errors for clearly invalid requests rather than silently acking. ## Rationale diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index b5f3aaaea..137e7f5d7 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -105,8 +105,8 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Safe point is server-determined.** The protocol does not prescribe what constitutes a safe point — for an LLM-based agent it may be between inference steps or tool calls; for a pipeline it may be between stages; for a batch job it may be at explicit checkpoints. The server defines this based on its execution model. - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. -- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params). This diverges from `tasks/update`/`tasks/cancel` (which ack unconditionally) because delivering queued messages to a completed task would create confusion about whether the steer had any effect. -- **Silent ack for invalid `taskId`.** Consistent with `tasks/update` and `tasks/cancel`, the server MUST ack even for invalid or nonexistent task IDs. +- **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params), because delivering queued messages to a completed task would create confusion about whether the steer had any effect. +- **Errors for invalid requests.** Consistent with `tasks/update` and `tasks/cancel`, servers SHOULD return `-32602` for unknown `taskId` or `requestState` integrity failures. #### Streamable HTTP @@ -251,8 +251,10 @@ type DetailedTask = | Already paused | `-32602` | `tasks/pause` | Task already in `paused` | | Not paused | `-32602` | `tasks/resume` | Task not in `paused` | | Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | +| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | +| Invalid requestState | `-32602` | Any | `requestState` fails integrity verification (SHOULD) | -`tasks/steer` silently acks for invalid `taskId`, consistent with `tasks/update` and `tasks/cancel`. +Consistent with SEP-2663's error model for `tasks/update` and `tasks/cancel`: servers SHOULD return errors for clearly invalid requests rather than silently acking. ## Rationale From 8490ce1e246f809e8514fd807a4bd81921092969 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Tue, 5 May 2026 16:04:04 -0700 Subject: [PATCH 11/13] SEP-2669: fix prettier formatting --- seps/2669-task-interaction.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index 137e7f5d7..ec833f32f 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -244,15 +244,15 @@ type DetailedTask = ### Error Summary -| Error | Code | Method | When | -| ------------------ | -------- | -------------- | --------------------------------------------- | -| Method not found | `-32601` | Any | Server doesn't support the method | -| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | -| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | -| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | -| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | -| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | -| Invalid requestState | `-32602` | Any | `requestState` fails integrity verification (SHOULD) | +| Error | Code | Method | When | +| -------------------- | -------- | -------------- | ----------------------------------------------------- | +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | +| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | +| Invalid requestState | `-32602` | Any | `requestState` fails integrity verification (SHOULD) | Consistent with SEP-2663's error model for `tasks/update` and `tasks/cancel`: servers SHOULD return errors for clearly invalid requests rather than silently acking. From 5f52689223873165b6bfe7cea53c6ddcdf0211c5 Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Sat, 9 May 2026 21:16:36 -0700 Subject: [PATCH 12/13] =?UTF-8?q?SEP-2669:=20rename=20ttlSeconds=20?= =?UTF-8?q?=E2=86=92=20ttlMs=20per=20latest=20SEP-2663?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/seps/2669-task-interaction.mdx | 2 +- seps/2669-task-interaction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 007ceac36..15ad52098 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -316,7 +316,7 @@ Steer messages are natural language instructions delivered to the task's executi ### `paused` state and resource holding -Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. +Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlMs` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. ### Handle security diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index ec833f32f..b621034bd 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -298,7 +298,7 @@ Steer messages are natural language instructions delivered to the task's executi ### `paused` state and resource holding -Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlSeconds` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. +Server-initiated pause could be used to hold resources (VMs, database connections) indefinitely. Servers SHOULD enforce TTL-based cleanup for paused tasks, consistent with SEP-2663's `ttlMs` field. Clients SHOULD monitor for tasks that remain paused beyond expected durations. ### Handle security From ef0d904d235718b38900c494cba663c14c8ef42c Mon Sep 17 00:00:00 2001 From: Pedram Rezaei Date: Mon, 11 May 2026 16:22:29 -0700 Subject: [PATCH 13/13] SEP-2669: remove requestState per SEP-2663 removal --- docs/seps/2669-task-interaction.mdx | 41 ++++++++--------------------- seps/2669-task-interaction.md | 41 ++++++++--------------------- 2 files changed, 22 insertions(+), 60 deletions(-) diff --git a/docs/seps/2669-task-interaction.mdx b/docs/seps/2669-task-interaction.mdx index 15ad52098..b261847d7 100644 --- a/docs/seps/2669-task-interaction.mdx +++ b/docs/seps/2669-task-interaction.mdx @@ -101,12 +101,6 @@ interface TaskSteerRequest extends JSONRPCRequest { * Delivered at the next server-determined safe point. */ message: string; - - /** - * Opaque best-effort state, round-tripped by the client. - * Servers MUST tolerate receiving a stale value gracefully. - */ - requestState?: string; }; } ``` @@ -124,7 +118,7 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. - **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params), because delivering queued messages to a completed task would create confusion about whether the steer had any effect. -- **Errors for invalid requests.** Consistent with `tasks/update` and `tasks/cancel`, servers SHOULD return `-32602` for unknown `taskId` or `requestState` integrity failures. +- **Errors for invalid requests.** Consistent with `tasks/update` and `tasks/cancel`, servers SHOULD return `-32602` for unknown `taskId`. #### Streamable HTTP @@ -144,12 +138,6 @@ interface TaskPauseRequest extends JSONRPCRequest { * ID of the task to pause. */ taskId: string; - - /** - * Opaque best-effort state, round-tripped by the client. - * Servers MUST tolerate receiving a stale value gracefully. - */ - requestState?: string; }; } ``` @@ -190,12 +178,6 @@ interface TaskResumeRequest extends JSONRPCRequest { * ID of the task to resume. */ taskId: string; - - /** - * Opaque best-effort state, round-tripped by the client. - * Servers MUST tolerate receiving a stale value gracefully. - */ - requestState?: string; }; } ``` @@ -262,17 +244,16 @@ type DetailedTask = ### Error Summary -| Error | Code | Method | When | -| -------------------- | -------- | -------------- | ----------------------------------------------------- | -| Method not found | `-32601` | Any | Server doesn't support the method | -| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | -| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | -| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | -| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | -| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | -| Invalid requestState | `-32602` | Any | `requestState` fails integrity verification (SHOULD) | +| Error | Code | Method | When | +| ------------------ | -------- | -------------- | ----------------------------------------------------- | +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | +| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | -Consistent with SEP-2663's error model for `tasks/update` and `tasks/cancel`: servers SHOULD return errors for clearly invalid requests rather than silently acking. +Consistent with SEP-2663's error model: servers SHOULD return errors for clearly invalid requests. ## Rationale @@ -333,4 +314,4 @@ All three methods accept `taskId` as their routing key. The same security model - Ryan Nowak (@rynowak) — original extensions proposal identifying steering, pause/resume, and rich output as the key gaps - Luca Chang (@LucaButBoring) — SEP-2663 design patterns that this extension follows - Caitie McCaffrey (@CaitieM20) — Agents WG sponsorship and tasks stabilization work -- Peter Alexander (@pja-ant) — `requestState` analysis that informed the ack-only pattern choice +- Peter Alexander (@pja-ant) — design feedback that informed the ack-only pattern choice diff --git a/seps/2669-task-interaction.md b/seps/2669-task-interaction.md index b621034bd..c8ac6b5f9 100644 --- a/seps/2669-task-interaction.md +++ b/seps/2669-task-interaction.md @@ -83,12 +83,6 @@ interface TaskSteerRequest extends JSONRPCRequest { * Delivered at the next server-determined safe point. */ message: string; - - /** - * Opaque best-effort state, round-tripped by the client. - * Servers MUST tolerate receiving a stale value gracefully. - */ - requestState?: string; }; } ``` @@ -106,7 +100,7 @@ type TaskSteerResult = Result; // empty acknowledgment, resultType: "complete" - **Queue semantics.** Multiple steer messages MAY be queued. Delivery order MUST match submission order. - **Accepted on `working` and `paused` tasks.** A task in `paused` state accepts `tasks/steer` — messages are queued for delivery when the task resumes. - **Rejected on terminal tasks.** A task in `completed`, `failed`, or `cancelled` status MUST reject `tasks/steer` with `-32602` (Invalid params), because delivering queued messages to a completed task would create confusion about whether the steer had any effect. -- **Errors for invalid requests.** Consistent with `tasks/update` and `tasks/cancel`, servers SHOULD return `-32602` for unknown `taskId` or `requestState` integrity failures. +- **Errors for invalid requests.** Consistent with `tasks/update` and `tasks/cancel`, servers SHOULD return `-32602` for unknown `taskId`. #### Streamable HTTP @@ -126,12 +120,6 @@ interface TaskPauseRequest extends JSONRPCRequest { * ID of the task to pause. */ taskId: string; - - /** - * Opaque best-effort state, round-tripped by the client. - * Servers MUST tolerate receiving a stale value gracefully. - */ - requestState?: string; }; } ``` @@ -172,12 +160,6 @@ interface TaskResumeRequest extends JSONRPCRequest { * ID of the task to resume. */ taskId: string; - - /** - * Opaque best-effort state, round-tripped by the client. - * Servers MUST tolerate receiving a stale value gracefully. - */ - requestState?: string; }; } ``` @@ -244,17 +226,16 @@ type DetailedTask = ### Error Summary -| Error | Code | Method | When | -| -------------------- | -------- | -------------- | ----------------------------------------------------- | -| Method not found | `-32601` | Any | Server doesn't support the method | -| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | -| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | -| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | -| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | -| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | -| Invalid requestState | `-32602` | Any | `requestState` fails integrity verification (SHOULD) | +| Error | Code | Method | When | +| ------------------ | -------- | -------------- | ----------------------------------------------------- | +| Method not found | `-32601` | Any | Server doesn't support the method | +| Invalid task state | `-32602` | `tasks/pause` | Task not in `working` or `input_required` | +| Already paused | `-32602` | `tasks/pause` | Task already in `paused` | +| Not paused | `-32602` | `tasks/resume` | Task not in `paused` | +| Terminal task | `-32602` | `tasks/steer` | Task in `completed`, `failed`, or `cancelled` | +| Unknown task ID | `-32602` | Any | `taskId` does not correspond to a known task (SHOULD) | -Consistent with SEP-2663's error model for `tasks/update` and `tasks/cancel`: servers SHOULD return errors for clearly invalid requests rather than silently acking. +Consistent with SEP-2663's error model: servers SHOULD return errors for clearly invalid requests. ## Rationale @@ -315,4 +296,4 @@ All three methods accept `taskId` as their routing key. The same security model - Ryan Nowak (@rynowak) — original extensions proposal identifying steering, pause/resume, and rich output as the key gaps - Luca Chang (@LucaButBoring) — SEP-2663 design patterns that this extension follows - Caitie McCaffrey (@CaitieM20) — Agents WG sponsorship and tasks stabilization work -- Peter Alexander (@pja-ant) — `requestState` analysis that informed the ack-only pattern choice +- Peter Alexander (@pja-ant) — design feedback that informed the ack-only pattern choice