Skip to content

discussion: FanInStep / Send naming asymmetry post-#245 #257

@miguelgfierro

Description

@miguelgfierro

The asymmetry

After #245 (the unification work), the pipeline package keeps FanInStep while FanOutStep is gone — replaced by the Send sentinel. Visually that looks inconsistent: if FanIn exists, where's FanOut?

The honest answer is that FanInStep and Send were never symmetric to begin with, and the old FanOutStep name was misleading. The deletion just exposed it.

Why they aren't opposites

Layer Where it lives What it does
FanInStep StepExecutor Runs at a node Merges multiple input ports into one output value via a Python merge_fn(list) -> Any. Basically lambda items: ....
Send Control-flow sentinel Returned from a node Tells the engine to dispatch N parallel workers, each on a copy of state with the Send's payload merged in.

You can't make Send a step. The act of "spawn N workers in parallel, each with its own state copy" can't be expressed as a single node's execute(context, inputs) -> Any — that's exactly why it has to be a sentinel the engine intercepts.

And the old FanOutStep didn't actually fan out either — it just returned a list. The parallelism (if any) was an emergent property of the downstream DAG topology, not something the step itself did. Renaming SendFanOutStep would actually be a regression: it would hide the fact that Send is the thing that causes parallel execution, with per-worker state copies.

Why this matters

The current names invite a wrong mental model:

  • A user sees FanInStep and asks "where's FanOutStep?" — searches, finds it's gone, gets confused about whether the framework supports fan-out at all.
  • A user finds Send and FanInStep separately and doesn't realize they're meant to compose, because the names suggest different domains.

Options

Option A — Rename FanInStepMergeStep (or JoinStep)

Kills the false symmetry. The name accurately describes what it does: merge N input ports at a node. No fan-in semantics are implied or required.

  • Small PR — steps.py rename + __init__.py re-export.
  • Breaking unless we keep FanInStep = MergeStep as a deprecated alias.
  • Aligns with our principle that names should describe behavior, not topology buzzwords.

Option B — Leave FanInStep alone

FanInStep is rarely used in practice (most users write a CallableStep with a merge lambda inline). The cost of the visual inconsistency is low; the cost of a rename + deprecation cycle is real.

  • No code change.
  • Accept that visual symmetry was already broken — documenting it in docs/pipeline.md is enough.

Option C — Add a Collect sentinel for true fan-in symmetry

Mirror Send with a Collect(targets) sentinel — "wait for these N nodes, then merge their state and pick the next step." This would give true symmetry: Send for parallel out, Collect for parallel in.

  • Bigger change. Requires engine support for "park current node until N targets complete."
  • Mostly redundant with what the topological scheduler already does for fan-in via DAG edges + reducers (Layer 3).
  • I'd skip this unless a real use case shows up — it adds API surface for marginal value.

Recommendation

Option A if we're already touching steps.py for #239 (the BranchStep/FanOutStep migration cleanup). Bundle the rename into that PR.

Option B if steps.py stays untouched for now. The asymmetry is documented; users who hit it can read the docstring.

Option C is parked unless someone needs collect-style join semantics that the existing topology + reducers can't express.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions