Long-running asynchronous jobs can look stalled when callers receive nothing until the final value is ready. LlamaIndex Workflows can publish typed progress events during a run while continuing toward its StopEvent result.
Each run returns a WorkflowHandler that represents both sides of this interaction. Workflow steps send ProgressEvent objects through Context.write_event_to_stream(), and the caller consumes them through handler.stream_events() without changing the typed events passed between steps.
The standalone llama-index-workflows package is sufficient for the local smoke test and does not require an LLM or API key. A handler stream can be consumed once, so applications with several listeners should read it through one consumer and fan out those events separately.
Steps to stream LlamaIndex workflow events:
- Define the event contracts at the top of stream-workflow-events.py.
- stream-workflow-events.py
import asyncio from workflows import Context, Workflow, step from workflows.events import Event, StartEvent, StopEvent class ProgressEvent(Event): message: str class DraftEvent(Event): text: str
ProgressEvent is visible to the caller, while DraftEvent remains the typed handoff between workflow steps.
Related: How to install LlamaIndex with pip - Append the workflow steps after the DraftEvent class.
class DraftWorkflow(Workflow): @step async def prepare(self, ctx: Context, ev: StartEvent) -> DraftEvent: ctx.write_event_to_stream( ProgressEvent(message=f"received topic: {ev.topic}") ) return DraftEvent(text=ev.topic.upper()) @step async def finish(self, ctx: Context, ev: DraftEvent) -> StopEvent: ctx.write_event_to_stream( ProgressEvent(message=f"prepared text: {ev.text}") ) return StopEvent(result=f"final: {ev.text}")
write_event_to_stream() exposes progress without replacing the DraftEvent returned to the next step.
- Append the stream consumer after the DraftWorkflow class.
async def main() -> None: workflow = DraftWorkflow(timeout=10) handler = workflow.run(topic="stream events") async for event in handler.stream_events(): if isinstance(event, ProgressEvent): print(f"{type(event).__name__}: {event.message}") result = await handler print(f"Final result: {result}") if __name__ == "__main__": asyncio.run(main())
The handler returned by run() supplies both streamed events and the final result from one run.
- Run the completed workflow script to confirm both progress events precede the final result.
$ python3 stream-workflow-events.py ProgressEvent: received topic: stream events ProgressEvent: prepared text: STREAM EVENTS Final result: final: STREAM EVENTS
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.