Events

Streaming a workflow run so you can watch progress live — built-in lifecycle events plus your own custom ones — all discriminated by a single `.type` string.

Part 49 of 72 Microsoft Agent Framework Python — Every Lesson

What this lesson demonstrates

A workflow doesn’t have to be a black box. As it runs, it emits events at every step, and workflow.run(x, stream=True) hands them to you one at a time as an async iterator — so you can show progress, log diagnostics, or relay domain data live instead of only seeing the final output.

The Python SDK has no per-kind event classes: every event is a single WorkflowEvent discriminated by a .type string — "executor_invoked", "executor_completed", "output", "intermediate", "error". Executors can also emit custom events with any type= string plus a data= payload.

One real excerpt

An executor emits a custom "progress" event, then the caller filters on .type:

class Summarize(Executor):
    @handler
    async def run(self, text: str, ctx: WorkflowContext[str]) -> None:
        await ctx.add_event(WorkflowEvent(type="progress", data="summarize: calling model"))
        reply = await agent.run(text)
        await ctx.send_message(reply.text.strip())

async for event in workflow.run(ARTICLE, stream=True):
    if event.type == "executor_invoked":
        print(f"  → invoked   : {event.executor_id}")
    elif event.type == "progress":            # our custom event
        print(f"  · progress  : {event.data}")
    elif event.type == "output":              # terminal, from ctx.yield_output
        print(f"  ★ output    : {event.data!r}")

The gotcha

Streaming is workflow.run(x, stream=True) — there is no run_stream(). And the types "started", "status", and "failed" are reserved for framework lifecycle: if an executor tries to emit one, the event is silently dropped and a warning logged. Note also that "output" is terminal output (via ctx.yield_output, read with result.get_outputs()) while "intermediate" is observational (read with result.get_intermediate_outputs()); "data" is a deprecated alias for "intermediate".

How it maps to Azure AI Foundry

The event plumbing is client-agnostic — but this lesson runs a FoundryChatClient agent inside one executor (FoundryChatClient + AzureCliCredential), so the streamed executor_invoked/progress/executor_completed/output sequence wraps a real model call. In production you’d forward the same events to a UI or an OpenTelemetry pipeline.

Run it

uv run tutorial/03-workflows/06_events.py

Needs Foundry credentials (az login). You should see interleaved invoked/progress/completed/output lines, then a [SUMMARY] block as the terminal output.


Next: Workflow Builder

Sources & References

The Python SDK this lesson exercises
Workflow events and streaming observability