Skip to content

Workflow Events

When you call ExecuteStreamingAsync(), the workflow emits a unified stream of events. These include both workflow lifecycle events (from the graph orchestrator) and agent events (text deltas, tool calls, etc.) from each node — all in one stream.

Use WorkflowEventCoordinator when you need approval responses or observers; otherwise call with just input:

csharp
await foreach (var evt in workflow.ExecuteStreamingAsync(input))
{
    switch (evt)
    {
        case WorkflowStartedEvent e:
            Console.WriteLine($"Workflow '{e.WorkflowName}' started ({e.NodeCount} nodes)");
            break;

        case WorkflowNodeStartedEvent e:
            Console.WriteLine($"  → Node '{e.NodeId}' started");
            break;

        case TextDeltaEvent e:
            Console.Write(e.Text);         // Token-by-token streaming from a node
            break;

        case WorkflowNodeCompletedEvent e:
            Console.WriteLine($"  ✓ Node '{e.NodeId}' done in {e.Duration.TotalSeconds:F1}s");
            // Access node outputs
            foreach (var (key, value) in e.Outputs ?? [])
                Console.WriteLine($"    {key} = {value}");
            break;

        case WorkflowCompletedEvent e:
            Console.WriteLine($"Workflow done. Success: {e.Success}, Duration: {e.Duration}");
            break;

        case WorkflowDiagnosticEvent e when e.Level >= LogLevel.Warning:
            Console.WriteLine($"[{e.Level}] {e.Source}: {e.Message}");
            break;
    }
}

Event Reference

WorkflowStartedEvent

Emitted once when the workflow begins execution.

PropertyTypeDescription
WorkflowNamestringName of the workflow
NodeCountintNumber of agent nodes in the graph
LayerCountint?Number of execution layers (null for cyclic graphs)
ExecutionContextAgentExecutionContextAgent hierarchy context. Contains AgentId (unique run ID), AgentChain (list of parent names), Depth (nesting level), and ParentAgentId. Useful for correlating events when a workflow is invoked as a toolkit capability inside a parent agent.

WorkflowNodeStartedEvent

Emitted when an individual node begins executing.

PropertyTypeDescription
WorkflowNamestringParent workflow name
NodeIdstringThe node's ID (as registered with AddAgent)
AgentNamestring?Name of the agent at this node
LayerIndexint?Parallel execution layer (null for sequential)

WorkflowNodeCompletedEvent

Emitted when a node finishes. Contains the node's outputs — use this instead of RunAsync().FinalAnswer for reliable output access.

PropertyTypeDescription
WorkflowNamestringParent workflow name
NodeIdstringThe node's ID
AgentNamestring?Name of the agent
SuccessboolWhether the node succeeded
DurationTimeSpanNode execution time
ProgressfloatCompletion percentage (0.0–1.0) across all nodes
OutputsIReadOnlyDictionary<string, object>?Node outputs keyed by output key
ErrorMessagestring?Error message if the node failed

Example — reading outputs from streaming:

csharp
if (evt is WorkflowNodeCompletedEvent node && node.NodeId == "writer")
{
    var report = node.Outputs?["report"] as string;
}

WorkflowNodeSkippedEvent

Emitted when a node is skipped (e.g. its incoming conditional edge did not match, or OnErrorSkip was triggered).

PropertyTypeDescription
WorkflowNamestringParent workflow name
NodeIdstringThe skipped node's ID
ReasonstringWhy the node was skipped

WorkflowCompletedEvent

Emitted once when the entire workflow finishes.

PropertyTypeDescription
WorkflowNamestringWorkflow name
DurationTimeSpanTotal workflow duration
SuccessfulNodesintNodes that completed successfully
FailedNodesintNodes that failed
SkippedNodesintNodes that were skipped
SuccessboolTrue when FailedNodes == 0

WorkflowLayerStartedEvent / WorkflowLayerCompletedEvent

Emitted for each parallel execution layer (a group of nodes that run concurrently in a fan-out).

PropertyTypeDescription
LayerIndexint0-based layer index
NodeCountintNodes in this layer (Started only)
DurationTimeSpanLayer duration (Completed only)
SuccessfulNodesintNodes succeeded (Completed only)

WorkflowEdgeTraversedEvent

Diagnostic event emitted when a routing edge is followed. Useful for debugging conditional routing decisions.

PropertyTypeDescription
FromNodeIdstringSource node
ToNodeIdstringTarget node
HasConditionboolWhether a condition was evaluated
ConditionDescriptionstring?Human-readable condition description

WorkflowDiagnosticEvent

Internal orchestrator log messages surfaced as events. Filter by Level to control verbosity.

PropertyTypeDescription
LevelHPD.MultiAgent.LogLevelTrace, Debug, Information, Warning, Error, Critical
SourcestringComponent that emitted it (e.g. "Orchestrator", node ID)
MessagestringDiagnostic message
NodeIdstring?Related node, if applicable

Note: Level is typed as HPD.MultiAgent.LogLevel, not Microsoft.Extensions.Logging.LogLevel. The two enums have the same numeric values and member names, but are distinct types. If you have both namespaces in scope, qualify the type explicitly to avoid an ambiguous reference error:

csharp
case WorkflowDiagnosticEvent e when e.Level >= HPD.MultiAgent.LogLevel.Warning:

Agent Events

All AgentEvent subtypes (from HPD.Agent) pass through the stream unchanged. This includes:

EventDescription
TextDeltaEventA token of streamed text from an agent
ToolCallStartEventA tool invocation is starting
ToolCallEndEventA tool invocation completed
MessageTurnFinishedEventAn agent's full turn completed (includes token usage)
NodeApprovalRequestEventThe workflow is paused, waiting for human approval

→ See 06.3 Node Options for how to respond to approval events.

Filtering events by agent

Every AgentEvent carries an ExecutionContext property with AgentName, AgentId, AgentChain (the full parent hierarchy), and Depth. Because all agent events from all nodes flow through the same stream, you can use ExecutionContext.AgentName to route or filter them per agent:

csharp
await foreach (var evt in workflow.ExecuteStreamingAsync(input))
{
    // Workflow lifecycle events don't have ExecutionContext — handle them first
    if (evt is WorkflowNodeStartedEvent nodeStart)
    {
        Console.WriteLine($"--- {nodeStart.NodeId} ---");
        continue;
    }

    // All agent events carry ExecutionContext
    if (evt is AgentEvent agentEvt)
    {
        var agentName = agentEvt.ExecutionContext?.AgentName ?? "unknown";

        switch (evt)
        {
            case TextDeltaEvent delta:
                // Only show text from the verifier node
                if (agentName == "Verifier")
                    Console.Write(delta.Text);
                break;

            case MessageTurnFinishedEvent finished:
                Console.WriteLine($"[{agentName}] turn finished — {finished.Usage?.OutputTokenCount} tokens");
                break;
        }
    }
}

AgentChain for nested agents — if a node runs a SubAgent internally, events from the sub-agent are also in the stream. AgentChain gives you the full hierarchy:

csharp
if (evt is AgentEvent agentEvt)
{
    var ctx = agentEvt.ExecutionContext;

    // Ignore events from sub-agents — only show root-level agent output
    if (ctx?.Depth == 0 && evt is TextDeltaEvent delta)
        Console.Write(delta.Text);

    // Or show indented output at every nesting level
    if (evt is TextDeltaEvent d)
    {
        var indent = new string(' ', (ctx?.Depth ?? 0) * 2);
        Console.Write($"{indent}{d.Text}");
    }
}

Depth is 0 for the workflow's direct agents, 1 for any SubAgent they spawn, 2 for sub-sub-agents, and so on.


Event Order

For a simple linear workflow (A → B → C), events arrive in this order:

WorkflowStartedEvent
WorkflowLayerStartedEvent   (layer 0)
WorkflowNodeStartedEvent    (A)
TextDeltaEvent ...          (streaming from A)
WorkflowNodeCompletedEvent  (A)
WorkflowLayerCompletedEvent (layer 0)
WorkflowLayerStartedEvent   (layer 1)
WorkflowNodeStartedEvent    (B)
...
WorkflowLayerCompletedEvent (last layer)
WorkflowCompletedEvent

For fan-out layers, all nodes in the layer emit their events interleaved, since they run concurrently.

Released under the MIT License.