Flows & orchestration

Business processes are graphs, not call stacks. Plugfy makes the graph a language construct: declarative, parallel, observable and deterministic.

A flow lives next to its rules

A module (.plug) is one namespace: types, functions, a decision, and flows — all in one file. Flows import the same libraries and call the same functions:

pricing.plug
namespace shop.pricing
import System.Collections        // List/Map + filter/map/fold/sum/any/all/find
import System.Flow               // FlowResult<T>, run/toJson/toDot

Steps form a dependency graph

Each step names a value and the action that produces it. Steps that do not depend on each other belong to the same wave — and pure waves run in parallel, automatically:

stats.flow
flow profile(xs: List<int>) -> Stats {
    total = sum(xs)      // wave 0 — pure, runs in parallel
    avg   = mean(xs)     // wave 0
    hi    = maxOf(xs)    // wave 0
    => Stats(total: total, avg: avg, hi: hi)   // wave 1 — joins the results
}

The guarantees:

A barrier serializes

Ordinary control flow (if / while / let / var) inside a flow is a sequential barrier — everything before it finishes, the region runs alone, everything after waits. Parallelism is something you can read from the source.

Observable end to end

A flow's execution is a structured result, not a log to grep. FlowResult<T> carries the final value plus a per-step trace, exportable as JSON or Graphviz:

terminal
$ plugfy explain stats.flow              # the inferred parallel waves
$ plugfy flow-graph stats.flow --dot     # the dependency graph, ready for Graphviz

Errors delegate, they don't vanish

A failing step short-circuits its dependents and is captured in the flow's result — with the step name, the error value, and the trace of everything that did run. Error handling is part of the graph, not an afterthought: you decide per flow whether a failure aborts, falls back, or is delegated upward as a value.

The road ahead: Conduit planned

The flow surface is growing into Conduit — one node grammar for rich orchestration. Planned additions (all additive, no new kernel opcodes):

fulfill.flow — planned surface
namespace Acme.Orders
import Acme.Inventory as Inv
import Acme.Payments  as Pay

@retry(max: 3) @tag("fulfillment")
flow Fulfill(in order: Order, in dryRun: bool = false) -> (out shipment: Shipment) {
    validate <- Orders.validate(order: order)             -> ValidOrder
    reserve  <- Inv.reserve(lines: validate.lines)        -> Reservation   // reserve and charge share
    @timeout(30s) charge <- Pay.charge(amount: validate.total) -> Receipt   // no data -> run in PARALLEL
}

See the roadmap for status.