The Event Stream
In the previous examples, we used.collect() to get the final result. When you call a Runnable, it doesn’t just return the answer - it returns a stream of events that tell you what’s happening step by step (an async generator). The .collect() method waits for all events and gives you just the final answer.
You can iterate through the async generator to process events in real-time:
- Monitor execution progress in real-time
- Handle streaming responses from LLMs
- Debug execution flow
- Build reactive user interfaces
Event Logging
In reality, you don’t need to manually print events. By default, events are logged to standard output by the framework. You can control logging behavior with these environment variables:TIMBAL_LOG_EVENTS: Which events to log (default:"START,OUTPUT")TIMBAL_LOG_FORMAT: Log format -"dev"for human-readable or"json"for structured (default)TIMBAL_LOG_LEVEL: Standard log level (default:"INFO")
Event Types
Events are the communication mechanism that Runnables use to stream information throughout their execution lifecycle. Every Runnable execution produces a sequence of events that can be consumed in real-time or collected for later processing. Events are designed to be lightweight. For comprehensive execution data, see Traces. Every execution produces at least a Start event (when it begins) and an Output event (when it finishes). LLMs and streaming operations also produce Delta events for intermediate results. Runs that pause for human input emit Approval or Interaction events. The following examples show what these events look like for an LLM interaction:Start Event
Signals the beginning of an execution.These fields are present in all events to identify their source - the framework handles this automatically. We’ll explore this in greater detail in advanced sections.
Delta Events
Streaming content is emitted as typed Delta Events — structured, semantic updates about text, tool calls, thinking, and other content types.Delta Event Structure
EachDeltaEvent contains an item property with a typed delta item. All delta items inherit from DeltaItem and have an id and type field:
Delta Item Types
DeltaEvents use a pattern of paired types: a “start” type for the beginning of a content block, and a “delta” type for streaming incremental updates. All items have anid field to correlate deltas with their parent block.
Text - Start of a text content block:
ToolUseDelta events will be emitted for a single tool call. You need to accumulate these deltas and parse the complete JSON afterwards.
Thinking - Start of LLM reasoning block:
Custom for streaming content that doesn’t fit standard types (e.g., multimodal content, provider-specific features, experimental data). This allows custom collectors and tools to emit arbitrary typed data while participating in the delta event system.
ContentBlockStop - Signals end of a content block:
Using Delta Events
Benefits of Delta Events
- Type Safety: Each delta item has specific fields and types
- Semantic Information: Know exactly what type of content is streaming
- Better Observability: Track tool calls, thinking, and text separately
- Block Lifecycle: Start and stop events for each content block
- UI Flexibility: Render different content types with appropriate components
- Structured Logging: Monitor different content types independently
Approval and Interaction Events
When a run pauses for human input, the framework emits structured events before the finalOutputEvent:
ApprovalEvent— arequires_approvalgate fired before a runnable executed. Resume withresume={approval_id: True}(or anApprovalResolution).InteractionEvent— a handler calledsuspend()to ask the user for input. Resume withresume={suspension_id: value}.
Output Event
Contains the final result and signals completion. Contains either the finaloutput result (if successful) or error information (if something went wrong).
Handling Errors
When a Runnable encounters an error during execution, it won’t raise an exception. Instead, it will always return anOutputEvent with error information. This ensures the event stream continues and you can handle errors gracefully: