Skip to content

.NET: DevUI workflows require complex Chat Protocol while Python workflows are simple - output visibility gap #2691

@joslat

Description

@joslat

It's fantastic having DevUI in .NET! However, while it works seamlessly for agents, getting a workflow with custom executors to work properly involves considerable effort due to architectural differences with Python.

The Good: Agents Work Great
DevUI with agents in .NET is straightforward - register an agent, or a workflow made of agents and it just works:

builder.Services.AddChatClientAgent<MyChatClient>("my-agent", "You are helpful.");
builder.Services.AddDevUI();

The Challenge: Custom Executor Workflows Require Significant Work, also not documented anywhere.
When moving beyond agents to workflows with custom executors, .NET developers face two issues:

  • Complex Chat Protocol pattern required (TurnToken handling, message accumulation)
  • Output from YieldOutputAsync() is not visible in DevUI

Problem 1: WorkflowOutputEvent content not displayed
When a workflow executor calls context.YieldOutputAsync(), a WorkflowOutputEvent is emitted. In .NET DevUI, this event falls through to the default case and produces an empty content array, resulting in no visible output.

Aspect .NET Python
WorkflowOutputEvent handling Falls to default case with empty content ✅ Explicitly converts to ResponseOutputItemAddedEvent with extracted text
ChatMessage extraction ❌ Not handled ✅ Extracts .Text property
String output ❌ Not handled ✅ Direct text assignment
Object/dict output ❌ Not handled ✅ JSON serialization
Display in DevUI ❌ No text visible ✅ Text displays properly
Python reference implementation (_mapper.py, lines 745-815) correctly handles WorkflowOutputEvent.

Problem 2: .NET requires complex Chat Protocol pattern that Python doesn't need
Aspect Python .NET (DevUI)
TurnToken required ❌ No ✅ Yes
Chat Protocol ❌ No ✅ Yes
Input handling DevUI parses input, sends directly to workflow.run_stream() Start executor must handle List AND TurnToken via ConfigureRoutes()
Start executor signature Simple: (text: str, ctx) or any type Complex: Must define handlers for two message types
TurnToken forwarding N/A Must manually forward TurnToken to next executor
Developer effort Minimal Considerable

Python Workflow Example (Simple)

class UpperCase(Executor):
    @handler
    async def to_upper_case(self, text: str, ctx: WorkflowContext[str]) -> None:
        result = text.upper()
        await ctx.send_message(result)
        
# DevUI just calls: workflow.run_stream(parsed_input)
# Output via ctx.yield_output() displays correctly

.NET Workflow for DevUI (Considerable Work)

public class StartExecutor : Executor
{
    private List<ChatMessage> _messages = new();
    
    protected override void ConfigureRoutes(IRoutesBuilder routes)
    {
        // Must handle BOTH message types
        routes.AddRoute<List<ChatMessage>>(HandleMessagesAsync);
        routes.AddRoute<TurnToken>(HandleTurnTokenAsync);
    }
    
    private async ValueTask HandleMessagesAsync(List<ChatMessage> messages, ...) 
    {
        _messages.AddRange(messages);  // Accumulate
    }
    
    private async ValueTask HandleTurnTokenAsync(TurnToken token, ...)
    {
        // Process accumulated messages
        var result = ProcessMessages(_messages);
        
        // Must emit AgentRunUpdateEvent (not YieldOutputAsync) for visibility
        await context.AddEventAsync(new AgentRunUpdateEvent(...));
        
        // MUST forward TurnToken or workflow hangs
        await context.SendMessageAsync(token, ct);
    }
}

Root Cause
.NET DevUI was built around the agent-centric Chat Protocol (designed for conversational agents with turn-based signaling via WorkflowThread). When workflows were added, they inherited this complexity. Python was designed with workflows as first-class citizens and uses a simpler direct invocation model.

Suggested Fixes

  • Immediate (Bug Fix): Update AgentRunResponseUpdateExtensions.cs to handle WorkflowOutputEvent similarly to Python - extract content from .Data property and convert to displayable ResponseOutputItem with TextContent.
  • Consider (Enhancement): Evaluate whether .NET DevUI could support a simpler workflow hosting path (like Python) that doesn't require Chat Protocol/TurnToken for custom executor workflows.

Impact

  • Agents: Work great ✅
  • Custom executor workflows: Require considerable work to:
  • Understand and implement Chat Protocol (TurnToken + message accumulation)
  • Work around YieldOutputAsync() visibility issue by using AgentRunUpdateEvent
  • Parity gap: Python workflows "just work" while equivalent .NET workflows require significant boilerplate

Files Involved

  • dotnet/src/Microsoft.Agents.AI.DevUI/AgentRunResponseUpdateExtensions.cs - Event conversion
  • WorkflowThread.cs - TurnToken sending logic
  • _mapper.py - Reference implementation (lines 745-815)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions