microsoft/autogen · error · InvalidOperationException

The stream should have returned the final result.

Error message

The stream should have returned the final result.

What it means

Task.RunAsync consumes StreamAsync and returns the first TaskFrame of FrameType.Response. If the stream completes without ever yielding a Response frame (every frame was text/output or the stream ended early), RunAsync throws InvalidOperationException — the task runner's contract is that the stream must end with a final result frame.

Source

Thrown at dotnet/src/Microsoft.AutoGen/AgentChat/Abstractions/Tasks.cs:86

    /// <remarks>
    /// The runner is stateful and a subsequent call to this method will continue from where the previous
    /// call left off.If the task is not specified,the runner will continue with the current task.
    /// </remarks>
    /// <param name="task">The task definition as a message.</param>
    /// <param name="cancellationToken"></param>
    /// <returns>The result of running the task.</returns>
    /// <exception cref="InvalidOperationException">If no response is generated.</exception>
    public async ValueTask<TaskResult> RunAsync(ChatMessage task, CancellationToken cancellationToken = default)
    {
        await foreach (TaskFrame frame in this.StreamAsync(task, cancellationToken))
        {
            if (frame.Type == TaskFrame.FrameType.Response)
            {
                return frame.Response!;
            }
        }

        throw new InvalidOperationException("The stream should have returned the final result.");
    }

    /// <summary>
    /// Run the task and produce a stream of <see cref="TaskFrame"/> and the final <see cref="TaskResult"/>
    /// is the last frame in the stream.
    /// </summary>
    /// <remarks>
    /// The runner is stateful and a subsequent call to this method will continue from where the previous
    /// call left off.If the task is not specified,the runner will continue with the current task.
    /// </remarks>
    /// <param name="task">The task definition as a string.</param>
    /// <param name="cancellationToken"></param>
    /// <returns>A stream of <see cref="TaskFrame"/> containing internal messages and intermediate results followed by
    /// the final <see cref="TaskResult"/></returns>
    public IAsyncEnumerable<TaskFrame> StreamAsync(string task, CancellationToken cancellationToken = default) =>
        this.StreamAsync(ToMessage(task), cancellationToken);

    /// <summary>

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Ensure the StreamAsync implementation always emits a TaskFrame(FrameType.Response, ...) before completing, even on early termination
  2. Prefer calling StreamAsync directly and handling frames yourself if the runner may legitimately end without a final response
  3. Audit middleware/agents in the pipeline for frames being filtered or the loop exiting before the response is produced

Example fix

// before (inside StreamAsync)
foreach (var f in inner) { yield return f; } // may end without Response
// after
TaskResult? final = null;
await foreach (var f in inner) { if (f.Type == TaskFrame.FrameType.Response) final = f.Response; yield return f; }
if (final is null) yield return new TaskFrame(TaskFrame.FrameType.Response, response: SynthesizeResult());
Defensive patterns

Strategy: try-catch

Try / catch

try { var result = await task.RunAsync(msg, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("final result"))
{
    // fall back to consuming the stream yourself: await foreach (var frame in task.StreamAsync(msg, ct)) { ... } 
}

Prevention

When it happens

Trigger: A custom or misconfigured task runner whose StreamAsync terminates without emitting a Response frame (e.g. an agent loop that stops on a limit without producing a final result); cancellation/error paths that close the stream instead of propagating the exception; a middleware chain that drops the final frame.

Common situations: Writing custom ITask implementations; combining agents/middleware that swallow the response frame; early-termination conditions (max-turn limits, stop words) that end the loop without a synthesized final response.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/9f24875cca060592. Report an issue: GitHub.