Compare
Parallelism and fan-out
Both systems can run work concurrently; their main difference is whether concurrency is explicit helper-driven work or graph-scheduled work.
Concurrency is not a reason by itself to select either framework. The design question is where the ownership of input, state updates, joins, and failure handling belongs.
PicoFlow: two scopes
Flow.concurrentSteps() batches independent flow executions through the application’s own
endpoint and returns each result to the coordinating flow. It is appropriate when each item
needs its own flow request and configuration.
This helper is coordinator concurrency, not an internal graph branch. It slices items by
batchSize, calls the app through SELF_URL, and invokes callbacks with each response. Every
child is an independent flow request with its own session. Network routing, authentication,
timeouts, retry policy, and partial failures therefore matter even when parent and child run in
the same deployment.
Step.runSteps() fans out nested child steps within an active flow and aggregates their
responses. It is appropriate for bounded child work whose parent owns the result. Nested child
steps are intentionally restricted: they must not use tool-driven transitions such as go,
stay, or direct, because they execute inside the parent execution frame. A tool in such a
child may instead return directResult(json): it stops that child after the tool call and
delivers the JSON value as the branch’s output, with no second model call or cursor change.
runSteps() creates a fresh Step instance and private memory/state view per invocation, then
publishes validated state at an explicit join. The default retain-successes policy keeps
fulfilled state when a sibling fails; atomic makes application-state publication all-or-
nothing. Repeated Step classes use stable branch keys and Step-owned reducers. Bounded
concurrency and cooperative cancellation are built in, but this remains an in-process join,
not a persisted work queue.
LangGraph: scheduler-level branches
LangGraph can schedule multiple nodes in the same super-step through graph topology, and its dynamic fan-out mechanisms can send work to downstream nodes. If branches update the same state key, the graph state needs a reducer that defines the join.
Normal multiple outgoing edges schedule their destinations concurrently in the next
super-step. Send supports map/reduce-style dynamic fan-out where each destination receives a
different input. Reducers are not optional decoration in that design: they define whether
parallel results append, add, merge, replace, or conflict. See the current
Graph API guide.
This makes parallelism visible in the graph itself, which is valuable when scheduling and joins are central to the workflow. It also means state-channel and reducer choices are part of normal graph authoring.
The direct hotel graph is sequential. Each agent routes to at most one tool node, and each tool node routes to at most one next agent. Its append reducers have not been exercised under parallel writes. LangGraph’s capacity for parallel super-steps is therefore a framework capability, not a benefit demonstrated by this particular implementation.
Join and failure semantics
| Question | PicoFlow concurrentSteps() |
PicoFlow runSteps() |
LangGraph branch/Send |
|---|---|---|---|
| Unit of work | Independent flow HTTP request | Child step inside one flow | Graph node invocation |
| State ownership | Separate child session | Private invocation drafts; canonical session state at join | Shared or per-Send graph input |
| Join owner | Parent callbacks/application code | Explicit calling Step plus Step-owned reducers | Scheduler plus state reducers |
| Durable progress | Whatever each child flow saves | Outer turn save or optional root-join checkpoint | Checkpoints when configured |
| Partial failure policy | Parent callback/HTTP policy | retain-successes (default) or atomic |
Graph retry/checkpoint/application policy |
| Dynamic fan-out | Input array | Explicit step request array | Send from routing logic |
Backpressure and resource limits
None of the abstractions removes the need for limits. Bound concurrency by provider quotas, database connections, tool capacity, and memory—not only CPU. Define what happens when one item times out, whether successful siblings may commit, and how a caller observes partial progress.
For LLM work, parallelism can reduce wall-clock time while multiplying token spend and rate- limit pressure. A deterministic tool fan-out and a model fan-out deserve different budgets.
A useful rule
Use PicoFlow helpers when a parent stage owns a small, bounded aggregation or coordinates independent flow requests. Use LangGraph fan-out when the graph runtime should own branching, scheduling, and reduction. In both cases, make external effects idempotent: concurrent session writes cannot undo an email, booking, or payment already sent to another system.