What is a Step?
A step is an isolated, retryable unit of work within a workflow. Steps are where the actual business logic executes - calling APIs, processing data, sending emails, etc. Each step runs on a Celery worker and can be retried independently if it fails.Key Characteristics
Isolated
Each step runs independently with its own retry policy and timeout.
Retryable
Failed steps automatically retry with configurable backoff strategies.
Cached
Completed step results are cached and replayed during workflow resumption.
Distributed
Steps execute on Celery workers, distributing load across your cluster.
Creating Steps
- Decorator
- Class
Configuration Options
- Decorator
- Class
Retry Strategies
Fixed Delay
Retry with a constant delay between attempts:Exponential Backoff
Retry with increasing delays (recommended for external APIs):Custom Delay in Error
Specify retry delay when raising an error:Error Handling
RetryableError
Use for transient failures that should be retried:FatalError
Use for permanent failures that should stop the workflow:Error Flow
Step Results and Caching
When a workflow resumes after suspension, completed steps are not re-executed. Instead, their cached results are returned:Parallel Step Execution
Execute multiple steps concurrently usingasyncio.gather():
Calling Primitives from Steps
Steps running within a durable workflow can call certain workflow primitives. When a step executes on a Celery worker, the worker automatically sets up a workflow context with the parent workflow’s metadata, enabling primitives to function.Supported Primitives
Starting Child Workflows from Steps
You can start child workflows from within a step usingstart_child_workflow(). Both wait_for_completion=True and wait_for_completion=False are supported:
Sleeping within Steps
sleep() works within steps but uses asyncio.sleep instead of durable suspension:
Unlike workflow-level sleep which frees the worker,
sleep() within a step holds the worker for the duration. If the worker crashes during the sleep, the state is lost. For long delays, prefer workflow-level sleep.Hooks are Not Supported from Steps
hook() and define_hook() cannot be called from within steps. Hooks require workflow suspension to wait for external events, which is not possible during step execution. Move hook calls to workflow-level code instead.
Force Local Execution
By default, steps in a Celery runtime are dispatched to worker processes via the message broker. For lightweight steps where the broker round-trip adds unnecessary latency, useforce_local=True to execute the step inline in the orchestrator process:
STEP_STARTEDandSTEP_COMPLETEDevents are recorded- Results are cached for replay during workflow resumption
- Retry policies (
max_retries,retry_delay) are respected - Cancellation checks still apply
Use
force_local for steps that are fast and lightweight (data merging, formatting, simple transformations). CPU-intensive or I/O-heavy steps should use the default distributed execution so they benefit from worker scaling.Best Practices
Keep steps small and focused
Keep steps small and focused
Each step should do one thing well. This makes retries more efficient - if a step fails, only that specific operation is retried.
Make steps idempotent
Make steps idempotent
Steps may be retried, so ensure they can be safely re-executed. Use idempotency keys when calling external APIs.
Use appropriate timeouts
Use appropriate timeouts
Set timeouts based on expected execution time. External API calls should have shorter timeouts than data processing steps.
Handle errors explicitly
Handle errors explicitly
Distinguish between retryable and fatal errors. Don’t retry errors that will never succeed.
Next Steps
Step Context
Share typed context data between workflows and distributed steps.
Events
Learn how event sourcing enables durability and replay.