Workflows execute steps based on their dependencies. By understanding how Timbal resolves these dependencies, you can build efficient pipelines that run steps in parallel when possible and sequentially when needed.Documentation Index
Fetch the complete documentation index at: https://docs.timbal.ai/llms.txt
Use this file to discover all available pages before exploring further.
Parallel by Default
When steps have no relationship between them, they run concurrently. Timbal doesn’t wait for one to finish before starting the next:Context Access
The input and output from each step is stored in its Run Context, accessible viaget_run_context(). Each step exposes two built-in variables:
.input: Contains a dictionary of all the parameters passed to the step. They are accessed through their name..output: Contains the value(s) returned by the step. Can be a single value, dictionary, array, or custom class.
| Method | Description |
|---|---|
current_span() | Current step’s data |
parent_span() | Parent workflow’s data |
step_span("name") | Any sibling step’s data |
Custom Variables
You can also create your own custom variables to share data between steps:process_user:
check_status:
Data Dependencies
When a step parameter references another step’s output via a lambda, Timbal automatically creates a dependency and enforces sequential execution:fetch_usersandfetch_ordersrun in parallel (no dependencies between them)merge_resultswaits for both to complete (its parameters reference their outputs)
fetch_users and fetch_orders haven’t executed when merge_results is defined, Timbal detects the step_span() references and knows to wait.
Handling Optional Steps
When a step might be skipped (e.g., due to awhen condition), you need to safely handle cases where that step didn’t execute. Use default=None with step_span() to check if a step ran before accessing its output.
Execution Flow
Consider a workflow that validates data and then processes it differently based on the validation result:merge_results tries to access process_valid or process_invalid, one of them won’t exist because it was skipped. Without default=None, accessing a skipped step would raise an error.
The Solution: Use step_span("name", default=None) which returns None if the step was skipped, allowing you to check which step actually ran.
Example
validate_dataalways executes and returns{"valid": True}or{"valid": False}- Based on the validation result, either
process_validorprocess_invalidruns (but never both) merge_resultsusesstep_span("name", default=None)to safely check which processing step ran:- If
step_span("process_valid", default=None)returns a span (notNone), that step ran - If it returns
None, the step was skipped, so we checkprocess_invalidinstead
- If
Key Point:
step_span("name", default=None) returns None if the step was skipped, allowing you to handle optional dependencies gracefully. Without default=None, accessing a skipped step would raise an error.Explicit Dependencies
Usedepends_on when you need ordering without a data dependency:
process_data waits for init_database even though it doesn’t use its output.
Summary
- Steps run in parallel by default
- Access sibling step data via
step_span(), custom variables viacurrent_span() - Lambda parameters automatically create dependencies — even if the referenced step hasn’t executed yet
- Use
depends_onfor explicit ordering without data dependency