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.
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
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.
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.
$ python3 stream-workflow-events.py ProgressEvent: received topic: stream events ProgressEvent: prepared text: STREAM EVENTS Final result: final: STREAM EVENTS