modelcontextprotocol / modelcontextprotocol/agents-wg
Steering
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 11
- Forks
- 9
- Avg merge
- 1h 43m
- Merged PRs (30d)
- 3
Description
Steering
Scopes landscape, what steering is, why it is needed, gaps in the current protocol, and path forward for working group to address the gaps.
Current state
Tasks augment server operations with a lifecycle.
Task creation is server-directed. There is no tasks/create; the client signals extension support per request (currently only tool/call), and the server decides whether to return a CreateTaskResult in place of the normal result.
Five states, of which working and input_required are non-terminal:
"working" | "input_required" | "completed" | "failed" | "cancelled"
Three methods and one notification for surface of operations: tasks/get, tasks/update, tasks/cancel, notifications/tasks.
Where multi-turn/multi-input interaction happens today
Inside a task. The server moves the task to input_required and publishes inputRequests; the client answers with tasks/update carrying inputResponses. Values in inputRequests are the union CreateMessageRequest | ListRootsRequest | ElicitRequest, and clients MUST treat each entry as they would the equivalent standalone server-to-client request, since a task is not a higher-trust channel.
Two properties matter later. inputRequests MUST contain all outstanding requests "that need to be fulfilled before the task can proceed", so it is the blocking set by definition. And a server MAY accept a partial set of responses, "in that case the task remains in input_required until the remaining responses arrive".
Before a task exists. Multi Round-Trip Requests handle input needed before the server commits to a task. The server returns an InputRequiredResult, ending the original request; the client retries with a new JSON-RPC id. The Tasks specification draws the line itself: MRTR before returning a CreateTaskResult, inputRequests during execution.
Outside the protocol's view. Many servers today implement some implicit form of SEP-2567 (Final) with server-minted state handles that the model threads through subsequent calls as ordinary tool arguments. The SEP is explicit that this is not a protocol construct but is "a tool-design pattern". Continuity is therefore carried by the model rather than the protocol, which means it has no proper lifecycle or real interoperability. A server COULD expose a steering-shaped tool taking a handle and an instruction using a pattern like requestState in MRTR.
What is steering
Steering is input delivered to an ongoing operation that MAY change what the task does next, including interrupting or redirecting work already in flight.:
Two forms:
- Server-prompted. The task asks and the client answers. This exists today as
input_requiredplusinputRequestsandtasks/update. Also aselicitationsfor other server operations outside oftool/call+task - Client-initiated. The client sends without having been asked. No mechanism exists.
Addressing both together is important because client initiated steering MAY resolve one or more server prompted steers (at minimum additional context to a server elicitation).
Examples, across both forms:
- Interruption: Client initiated block on progress of a task without any guidance
- Information or Context Steer: filling a form the server discovered it needed or adding context that may have been previously missed
- Correction Steer: correcting a wrong assumption twenty minutes into an analysis; narrowing the scope of a migration that is doing more than the caller now wants; redirecting a research run after its premise changed upstream.This generally takes a form of interruption and information combined
- Approval: approving an action before the server proceeds. Human in the loop flows similar to elicitation; may also be pre-empted by the client.
Why steering
Steering largely comes down to being able to re-utilize existing execution state.
- A wrong assumption, caught late. 20 steps into an analysis built on a bad premise. Restarting discards 19 stateful steps to fix the 20th.
- "This should be green, not blue." The roadmap's example: a small, late correction where restart cost is out of all proportion to the change.
- Narrowing scope mid-flight. Bounding a task with memory of what was already explored.
- Context that arrived late. A credential or an upstream decision that did not exist at kickoff. The server never asked, so
input_requirednever opened. - An orchestrator narrowing a subagent. The host is itself an agent whose inputs changed after it delegated. The ordinary case in agent-to-agent deployments.
Current gaps
No carrier for client-initiated steering. A client holding a task in working can read it with tasks/get or end it with tasks/cancel. tasks/update takes exactly taskId and inputResponses, keyed to requests the server issued. No message, instruction, or guidance field exists in the extension.
No representation of an outstanding but non-blocking request. Because inputRequests is defined as everything that must be fulfilled before the task can proceed, and the task stays in input_required until every outstanding key is answered, the set is blocking by construction. A server that wants something but can usefully keep working has nowhere to put the request: publishing it forces input_required interruption. A2A hit the same case and is worth looking into.
Withdrawal is observable only by absence. The specification allows a request to be superseded, but the only signal is the key disappearing from the next inputRequests snapshot. A client must diff snapshots to notice, and given the delivery guarantees below it may never observe it.
No acknowledgement of the right shape. The tasks/update ack is empty and eventually consistent: the server MAY ack before the observable status reflects the responses. Nothing tells a sender its input was acted on or if the inputRequest is still pending.
Delivery is specified as no guarantee. Task status notifications are optional and nothing requires a server to emit every transition.
No ordering. notifications/tasks carries no sequence number and the Tasks specification contains no ordering language at all.
Shape of the solution space
Nothing here is a recommendation, and several of these compose.
Where is steering carried? Relax the precondition on tasks/update so it accepts content not keyed to an outstanding request; add a task operation; or carry it as metadata on an existing path. The first is the smallest schema change but it redefines a field specified today as a response to a server-issued key. A new operation is explicit and costs a method. Metadata is cheapest and hardest to actually make interoperable. A fourth option already works and is invisible to the protocol: a server-defined tool taking a state handle, per SEP-2567.
What is the server obliged to do? A design should say whether a server MAY ignore the input, SHOULD acknowledge it, or MUST reflect it in an observable state change, and what a Tasks-supporting but steering-unaware server does when steered: reject, or accept and discard.
Two sub-questions the WG should settle explicitly or deliberately decide are out of scope. Are steering inputs queued? If a server accepts and defers, the sender needs some way to know it was read. If a server interrupts, the design has to say what happens to output already generated and in flight. How do steering inputs interleave with intermediary results? If increments acquire a sequence or cursor, that ordering is the natural anchor for relating an input to the output that reflects it.
What is the relation to elicitations? Strict adherence means steering enters the existing inputRequests/inputResponses machinery, inheriting its trust model and its rules for free. It also inherits the premise that the server framed the question, which client-initiated steering does not.
What happens when several requests are outstanding? Today every outstanding key must be answered before the task leaves input_required, and a partial set leaves it there. If steering enters this machinery, the WG has to decide whether steering inputs join the blocking set, and whether all-or-nothing should survive contact with a server that can proceed on partial information.
Should cancel-and-restart be made to work properly instead? Cancel-and-restart is what implementations CAN do today, and it is a legitimate destination. What it lacks is continuity: the replacement task has no relationship to the one it replaces. An identifier or state would let a server carry work forward though with no obligation to do so. A2A has the analogous field, reference_task_ids on Message. This is plausibly in scope for Tasks itself rather than for a steering design, and it may serve several of the use cases above
Guiding discussion questions
Is correlation required? The gaps above note that nothing ties later output to the input meant to influence it. This should determine if steering needs sequencing, and therefore how tightly it couples to the intermediary results work.
Is input_required addressed to the host or the user? Verifiably both today. Its values are CreateMessageRequest | ListRootsRequest | ElicitRequest; sampling and roots are answered by the host with no human involved, elicitation by a user through the client, and nothing on the wire marks which. The specification is inconsistent with itself, describing the state as "input from the client" where it lists the states and as "an elicitation to the user" in its worked example. Under SEP-2577 sampling and roots are Deprecated, so the union is collapsing onto the member whose text presumes a human, at the point where agent-to-agent chains need the opposite. A2A considered the same question and deliberately kept its equivalent state audience-agnostic, on the grounds that this is what lets one construct serve both user-to-agent and agent-to-agent.
Which of this is interaction state and which is execution state? The test: does the client do something different because of this field? If yes, it is interaction state and belongs on the wire. If it only reports what the server's scheduler is doing, it is execution state that has leaked. An acknowledgement that steering was received passes, because the sender behaves differently if it never arrives or if more information is required. A report of how guidance was incorporated probably fails.
Core specification or extension, and on what basis? Decide on what is expected to land in the main Task specification versus what remains an official extension component (especially if this leaks across multiple primitives like elicitations)
References
- Tasks extension, SEP-2663
- MCP
2026-07-28specification, MRTR, subscriptions, deprecations - SEP-2567, sessionless MCP via explicit state handles
- SEP-2577, deprecating sampling, roots and logging
- a2aproject/A2A#1582 (closed), four candidate behaviours for a message arriving at a working task, none made normative
- a2aproject/A2A#1992, multi-turn epic, Gap 1
- a2aproject/A2A#2125, steering as an opt-in A2A extension
- a2aproject/A2A#2129, #2149, ordering and additive input-required, both open
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the Tasks extension and SEP-2663, then compare the listed gaps with the Multi Round-Trip Requests and subscriptions specifications. Review the linked A2A discussion and references to identify the open design decisions; done means the working group has agreed on a concrete, interoperable steering direction.
Written by the indexing model from the issue text.
Assessment
- Domain
- api, backend-api-design, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100