modelcontextprotocol / modelcontextprotocol/agents-wg

Intermediary Results

Open
#26 1 comment 0 reactions 0 assignees View on GitHub

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/progress and notifications/message notifications MUST NOT be sent on the subscriptions/listen stream 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 statusMessage or a private _meta blob.
  • 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.