Behaviour for a single step in a workflow.
Each step receives its own params map and an upstream map containing
the results of exactly the steps it declared as dependencies —
direct dependencies only, never transitive ancestors. Steps return
{:ok, result} or {:error, reason}; a result may be any term.
Step ids are opaque terms: atoms in hand-written pipelines, strings when they come from parsed IR. The runner compares ids by value and never converts them.
Graceful degradation (convention, not enforcement)
Conditional steps stay in the DAG and no-op — returning a result like
{:ok, %{"skipped" => true}} — rather than being excluded from the
graph. The DAG stays static and analyzable; downstream steps read the
skip marker from upstream and degrade in turn.
Usage accounting (convention)
Steps that spend put a "usage" map in their result
(%{"input_tokens" => _, "output_tokens" => _, "calls" => _});
MimirWorkflows.Result.usage/1 folds them across a run.