What is a step?
A step is a durable operation within a workflow. When a step completes, its output is saved to the database. If the workflow crashes, completed steps return their cached results on replay - no re-execution, no duplicate side effects.Step execution model
Unlike workflows, which are scheduled by the orchestrator and assigned to workers, steps execute directly on the same worker running the workflow. This means:- ✅ Lower overhead - No scheduling delay
- ✅ Same execution context - Access to workflow variables
- ✅ Durability guaranteed - Output is persisted before continuing
Core step methods
step.run()
Run any Python function as a durable step:
- ✅ External API calls
- ✅ Database operations
- ✅ File I/O
- ✅ Non-deterministic operations (
random(),datetime.now()) - ✅ Any operation that might fail
- ❌ Pure logic (if statements, loops)
- ❌ Variable assignments
- ❌ String manipulation
step.invoke()
Start a child workflow without waiting for it to complete:
step.invoke_and_wait()
Start a child workflow and suspend until it completes:
step.batch_invoke()
Start multiple workflows in parallel without waiting:
step.batch_invoke_and_wait()
Start multiple workflows in parallel and wait for all to complete:
step.agent_invoke()
Start an agent execution without waiting for it to complete:
step.agent_invoke_and_wait()
Start an agent execution and suspend until it completes:
step.batch_agent_invoke()
Start multiple agent executions in parallel without waiting:
step.batch_agent_invoke_and_wait()
Start multiple agent executions in parallel and wait for all to complete:
step.wait_for()
Pause execution for a duration:
seconds, minutes, hours, days, weeks
step.wait_until()
Wait until a specific datetime:
step.wait_for_event()
Wait for an external event:
step.suspend()
Suspend execution and wait for manual resume. This is syntactic sugar around wait_for_event that’s tailored for agent human-in-the-loop flows.
step.publish_event()
Publish events durably:
step.uuid()
Generate a UUID that persists across replays:
step.now()
Get current timestamp that persists across replays:
step.trace()
Add custom spans for observability:
Step keys must be unique
Each step needs a uniquestep_key per execution:
Agents and steps
In Polos, agents are special workflows. Everything an agent does is broken into steps: Agent actions as steps:- ✅ LLM calls - Each call is a step
- ✅ Guardrail evaluation - Each guardrail is a step
- ✅ Stop condition checks - Each check is a step
- ✅ Tool calls - Tools are subworkflows (not steps)
- Agent failures resume from the last completed step (not the beginning)
- Tool calls are durable subworkflows (not lost on failure)
- Every agent action is observable via step events