modelcontextprotocol / modelcontextprotocol/agents-wg
Intermediary Results
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 11
- Forks
- 9
- Avg merge
- 1h 43m
- Merged PRs (30d)
- 3
Description
Intermediary Results
Scopes landscape, what intermediary results are, why it is needed, gaps in the current protocol, and path forward for working group to address the gaps.
Current state
Tasks gives long-running server operations a lifecycle, and a host/client observes it in one of two ways: polling tasks/get, respecting pollIntervalMs, or subscribing via subscriptions/listen with taskIds and receiving notifications/tasks. Both carry the same payload:
interface Task {
taskId: string;
status: "working" | "input_required" | "completed" | "failed" | "cancelled";
statusMessage?: string; // MAY be exposed to the end-user or model
createdAt: string; // ISO 8601
lastUpdatedAt: string; // ISO 8601
ttlMs: number | null;
pollIntervalMs?: number;
}
type DetailedTask =
| WorkingTask // Task
| InputRequiredTask // Task & { inputRequests: InputRequests }
| CompletedTask // Task & { result: { [key: string]: unknown } }
| FailedTask // Task & { error: JSONRPCErrorObject }
| CancelledTask; // Task
WorkingTask adds no fields. While a task runs, its observable surface is a status enum and an optional string. result appears at the end.
Similar mechanisms to Intermediary Results and what is missing:
| Mechanism | Payload | Why it does not carry an intermediate result |
|---|---|---|
notifications/progress |
progressToken, progress number, optional total, optional message string |
A number and a string. No content blocks, no structured data, no chunk, offset, or sequence. Forbidden on tasks, see below. |
notifications/tasks |
A complete DetailedTask |
A whole snapshot, not a delta. While working the snapshot contains no output. |
tasks/get |
The same DetailedTask |
Polling returns the same empty surface more often. |
statusMessage |
One optional string | Progress descriptions only. No accumulation semantics. |
Resources plus subscriptions/listen |
notifications/resources/updated carries the changed uri and nothing else |
Invalidation by URI. The client MUST call resources/read to get anything, and gets the whole resource when it does. |
resource_link |
A descriptor: uri, name, optional mimeType and others |
Carries no contents, and appears only in a final tool result. A pointer handed over at the end. |
notifications/message |
level, optional logger, data as arbitrary JSON |
Could structurally carry chunks, but Deprecated under SEP-2577 and forbidden on tasks. |
Two of those are shut off for tasks and are also not intended to carry agent-facing details:
notifications/progressandnotifications/messagenotifications MUST NOT be sent on thesubscriptions/listenstream for a task, and are not supported on tasks in general in this specification.
What are intermediary results
Output produced by an operation before it terminates, made available to the caller while the operation continues.
Examples, spanning agentic and non-agentic producers:
- Build and test logs. Lines accumulating from a CI run that has a final verdict but is not there yet.
- Agent tool calls. The steps an agent has taken so far, surfaced as it takes them.
- Stage outputs from a DAG. A pipeline completing node by node, where each finished node is retrievable before the graph is.
- Rows of a bounded result set. Price per security, one at a time, for a number of securities. The final answer is a list; the caller wants it as it fills. Concrete use case for structured data / list streaming.
Two related items that do not need to be solved here but are worth considering: Delivering large content may be a transport concern and unbounded output, like a subscription feed has a different lifecycle (see events/triggers). The scope of this SHOULD be output with a final form that the caller wants to watch accumulate but open to discussion here.
Why intermediary results
- Long operations are opaque. A task that runs for minutes shows a status string and nothing else. Callers cannot distinguish slow progress from a stall, and the only remedy is
tasks/cancel. - Partial output is often actionable before it is complete. A test run that has already failed, a migration that has processed the first thousand rows, three of five sections drafted. Waiting for terminal state discards some of the value of knowing early, including the evidence needed to decide whether to cancel.
- Hosts have nothing to render. Without a partial surface there is nothing to show between kickoff and completion, which pushes implementations toward stuffing content into
statusMessageor a private_metablob. - Steering has nothing to act on. A caller who cannot observe in-flight output has limited basis on which to intervene.
Current gaps to address
No partial-output surface at any level. Not on the task, not on tools/call. Related: modelcontextprotocol#3237 records that Tasks has no interoperable surface for partial output and notes the only home for it today is a private _meta blob; #2452 records that the Tasks design assumes nothing is available until completion; #2932 records the same for tools generally. PR #2632 proposes adding structured content to progress notifications and is open and unmerged.
Nothing to diff. Iterating tasks/get, or subscribing and reading notifications/tasks, is a workaround with full reads, and a working task has no output in it, so the iteration returns status and timestamps until result appears all at once.
No delta representation one layer down either. Using resources as the carrier is similar. resources/read returns whole contents: no revisions, event timeline or object deltas. notifications/resources/updated says only that a URI changed.
No way to detect a gap. There is no sequence number on notifications/tasks or on resource updates, task status notifications are optional so nothing requires transitions to be emitted. No idempotency keys to ensure that something with steering can be addressed.
Shape of the solution space
Carrier of an intermediate result? Perhaps a URI-addressable resource, reusing resources/read and subscriptions/listen, or part of the task's own output surface? The resources route is attractive because it reuses an existing primitive. Resources today have no revision, no delta, and no append semantics, so building on them means specifying streamable resources. The alternative framing, which A2A reached, treats increments as events + chunks of the eventual output.
Increment: what is the unit? A snapshot plus a cursor, so a reader can ask what changed since a known point. Append-only chunks, where the reader concatenates and the producer marks the end or an explicit delta a defined base. These differ in who holds reassembly state and in what happens after a gap, which matters most given there is no replay. The design has to say whether a producer MUST emit increments in order, whether a consumer MAY skip, and what a consumer does on discontinuity. Perhaps full task get(s) are sufficient here as a reconciler of state.
Delivery: how does the reader learn? Polling, notification, or the channels in the experimental-ext-triggers-events. Potentially out of scope here.
Where this meets steering. If increments acquire a sequence or cursor, that ordering is also the natural anchor for correlating an injected steering input to the output that reflects it.
Guiding discussion questions
Is an intermediate result output, or is it status (or could be either)? If it is output it needs addressing, ordering, and reassembly, and probably does not belong in a status notification at all. If it is status it can stay small and unstructured, and statusMessage is already most of the way there.
Which of this is interaction state and which is execution state? 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. intermediate-results design in my opinion should avoid becoming another trace channel like logging.
Is this Tasks work, Resources work, or transport work? The gap is open against plain tools/call as well as against Tasks, which suggests it may not be unique to this WG's surface but task COULD be a carrier of it.
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)
References
- Tasks extension and SEP-2663
- MCP
2026-07-28specification, progress, subscriptions, resources, deprecations - modelcontextprotocol#3237, no interoperable surface for partial output of a running task
- modelcontextprotocol#2452, in-progress results
- modelcontextprotocol#2932, no mechanism for tools to stream incremental results
- modelcontextprotocol#2632, structured content for progress notifications, open
- SEP-2577, deprecating sampling, roots and logging
- experimental-ext-triggers-events, draft events proposal
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 linked issues #3237, #2452, #2932 and PR #2632. Review the current progress, task, subscription and resource specifications; done requires an agreed scope and direction for intermediary results, including its carrier, increment model and delivery path.
Written by the indexing model from the issue text.
Assessment
- Domain
- api, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100