Real-world Reflow
A tutorial series that teaches Reflow through small runnable projects. Each post solves one problem in one domain with one SDK, in under 200 lines of code, with at most one library beyond the SDK itself.
What Reflow is
Reflow is a runtime for reactive flow graphs. You declare nodes and the edges between them; Reflow runs each node when one of its inputs changes.
- Reactive. A node only does work when something asks it to.
- Graph. Connections are explicit data, not buried inside function calls.
The actor model, multi-language SDKs, pack format, and wasm runtime all serve those two ideas.
The shape
Three concepts cover most of what you write.
Actor. A unit of work with named inports and outports. The
runtime calls run when messages arrive on its inputs; the actor
emits messages on its outputs.
flowchart LR
in([in])-->Doubler-->out([out])
classDef port fill:#e8eef7,stroke:#5a6f96,color:#23314f
class in,out port
class Doubler extends Actor {
static inports = ["in"];
static outports = ["out"];
run(ctx) {
ctx.send({ out: Message.integer(2 * ctx.input.in.data) });
ctx.done();
}
}
Graph. A description of which actors exist and which ports connect to which. Plain JSON. The same graph runs from any SDK.
flowchart LR
A[a: doubler] -- out → in --> B[b: collector]
const g = new Graph("demo");
g.addNode("a", "tpl_doubler");
g.addNode("b", "tpl_collector");
g.addConnection("a", "out", "b", "in");
Network. The runtime that ticks the graph. You hand it a
Graph, register the actor implementations the graph references,
and call start.
const net = new Network(g);
net.registerActor("tpl_doubler", new Doubler());
await net.start();
Comparison to a UI signal library
A SolidJS signal recomputes whenever its tracked dependencies change:
const [count, setCount] = createSignal(1);
const doubled = createMemo(() => count() * 2);
createEffect(() => console.log(doubled()));
setCount(5); // logs 10
flowchart LR
count((count signal)) -.tracked.-> doubled[doubled memo]
doubled -.tracked.-> effect[log effect]
The same pipeline in Reflow:
flowchart LR
source[source: input] --> doubler[doubler] --> logger[logger]
g.addNode("source", "tpl_input");
g.addNode("doubler", "tpl_doubler");
g.addNode("logger", "tpl_log");
g.addConnection("source", "out", "doubler", "in");
g.addConnection("doubler", "out", "logger", "in");
The graph is the dependency graph. Solid infers it from code; Reflow asks you to write it down. The trade pays back at the scale where graphs are usually authored visually in Zeal and exported as JSON.
Async reactivity
Solid's reactivity is synchronous and in-process. Reflow's is
asynchronous: actors return Futures, messages travel over
channels (in-memory, cross-process, or across the network), and the
runtime schedules execution.
flowchart LR
A[ingest] --> B[validate]
B --> C[enrich]
B --> D[score]
C --> E[merge]
D --> E
E --> F[persist]
classDef parallel fill:#fef3c7,stroke:#a16207,color:#3a2c08
class C,D parallel
The two highlighted nodes have no dependency between them, so the
runtime runs them concurrently. The shape of the graph implies the
parallelism — no Promise.all.
What this gives you:
- Concurrency. Independent actors run in parallel; no manual
Promise.all. - Back-pressure. Channels are bounded; a slow consumer throttles its producer.
- Streams. A port can carry a byte stream (audio, video, large blobs) alongside discrete-message ports.
- Replayability. An actor's input is a sequence of messages; the same inputs reproduce the same run.
- Portability. The graph is JSON. The same graph runs from Node, Python, Go, JVM, C++, or a browser tab — same Rust core compiled per target.
When Reflow fits
Use Reflow when the work is shaped like a pipeline:
- Stream of inputs to stream of outputs.
- Mixed I/O and CPU work that benefits from concurrent stages.
- The pipeline body changes over time and you want it as data, not code.
- You want the same logic to run in the browser and on a server.
Skip Reflow when the work is shaped like a request:
- One input, one output, no fan-out.
- A page of imperative code with no reusable stages.
- A CRUD endpoint where the framework you already use is fine.
Series outline
- Reactive particle field (Browser, Node SDK). Animation-frame driven graph rendering 200 spring-physics particles to canvas2D. Introduces actors, the graph, and the runtime contract.
- Live edits over a stream (Browser, Node SDK). Wikipedia's public SSE feed driving a Reflow graph. Same actor primitives, network-paced source.
- Multi-agent orchestration (Python). Three LLM agents run in parallel against a local Ollama model; a synthesizer combines their findings; per-token streaming through the graph.
- A concurrent worker pool over gRPC (Go).
The canonical
goroutines + channelsfan-out fetcher pool expressed as a graph: a long-running gRPC server where each call spins up a fresh per-request network — Dispatcher → N Fetchers → Sink — and streams pages back over server-streaming RPC. - Parallel data enrichment behind Spring Boot
(Java). Per-request graph behind a REST endpoint: Splitter fans
the SKU out to three slow downstream services, the Merger
awaits all three (
awaitAllInports) and returns a merged JSON payload — Reflow'sCompletableFuture.allOf().join(). - A long-running Kafka stream router
(Java). Daemon graph driven by the Kafka poll loop:
OrderSourcepublishes records viactx.send,Routerpicks one of four outports based on status, sinks fan in parallel with loggers for operational visibility. - Composing a workflow from the catalog
(Python). Issue triage that reads, decides, and acts. Almost
every node is a catalog template instantiated by id —
api_github_list_issues,tpl_loop,tpl_switch,api_slack_send_message. Three small custom actors fill the gaps. Demonstrates the third lifecycle (triggered batch), conditional routing as configuration, and the api_services pack model. - A polyphonic synthesizer with
ctx.pool(C++). Three voices, a mixer, a WAV file. The mixer holds avoicespool — one inport, variable-N upstreams, no port-per- voice explosion. Also showcasesStreamProducerfor high-throughput producer messaging andctx.sendfor mid-tick flush. - Reflow inside an Airflow PythonOperator
(Python). Daily triage with Airflow owning the calendar
(schedule, backfill, retry, UI, credentials store) and Reflow
owning the actor graph. The integration boundary is one
python_callablewhose body is a regular Reflow Network. - A graph that spans two processes
(Rust + CLI). Two peers federated through the bundled
reflow-discoveryserver. Same shape works for two machines. Auto-reconnect with backoff, auth-token gating on the accept path, periodic discovery refresh — everything the in-process series didn't need.
After tutorial 01 you will know enough Reflow to read the others in any order.