The orchestration gap.
Typed Go services solve business logic. Lazy streams solve data flow. LLM SDKs solve AI calls. Resilience libraries solve failure. Nothing solves the composition between them.
The five gaps we found
GAP 01 · Type erasure at composition
A typed action A returns ARes. A typed action B takes BReq. When you chain them in plain Go, you write the mapping manually. When you chain them in a workflow engine, the types disappear into YAML or Python dicts. Nobody keeps the type at the boundary.
GAP 02 · Streams as an afterthought
Files, HTTP chunks, SSE, LLM token streams, Kafka topics. Every Go project rewrites the same iterator plumbing: context cancellation, backpressure, early stop, error propagation. There is no shared primitive. Everyone rebuilds it slightly wrong.
GAP 03 · Resilience as middleware soup
Retry, circuit breaker, rate limit, idempotency, deduplication, coalescing. Six libraries. Six configuration formats. Six ways to fail. Composition of the failures — "if A fails, try B, but debounce duplicates and cache the success" — is hand-written every time.
GAP 04 · AI as a foreign body
AI SDKs are Python-first, or TypeScript-first, or a Go wrapper over HTTP that cannot participate in a typed Go pipeline. Structured output, model fallback, repair loops, and tool calling all live in their own ecosystem, disconnected from the rest of the service.
GAP 05 · Deployment as infrastructure
Temporal, Airflow, and Argo ask for a scheduler cluster, a database, a set of workers, and a deployment model of their own. The workflow engine becomes the biggest operational surface in your system. The glue was supposed to be the smallest.
What we built instead
Nexss Flow is a small language and a small runtime that close all five gaps at once:
- Actions stay typed in Go.
action.New[Req, Res]is the boundary. The DSL composes them without touching business logic. - Streams are first-class. Operators accept and return
iter.Seq2[T, error]. Cancellation and backpressure come from Go itself. - Resilience is composition syntax.
||,&,loop(...) until(...),:retry,:cache,:coalesce. One language, no middleware stack. - AI participates like any other action. Providers register as actions.
:typed=Verdictattaches a schema. Fallback uses the same||. - Deployment is a binary.
nexssflow build app.nflow -o app. No daemon, no scheduler, no database.
When to use something else
We are not trying to replace every orchestrator. Be honest about the boundaries.
Use Temporal when
You need multi-week, cross-team, stateful workflows with first-class human intervention and a managed persistence story. Temporal's durable execution model and its server-side coordinator are the product, not the glue.
Use Airflow when
Data engineers own the workflow, and the work is batch ETL on a scheduler. The Python ecosystem, the operator catalogue, and the lineage UI are the product.
Use LangGraph when
Your system is only the LLM. LangGraph's agent primitives, checkpointers, and Python ecosystem are the product. It does not need to orchestrate a database migration alongside it.
Use Benthos when
The workload is pure stream transformation: Kafka in, Kafka out, with mapping. Bloblang's expression language and connector catalogue are the product.
Use Nexss Flow when
The graph itself is the product. You have typed Go services, you have streams, you have AI, you have resilience needs, and none of them alone justifies a scheduler. You want one readable language that names the composition without making the runtime the biggest system in your deployment.
What we explicitly refuse to do
- No DSL for business logic. A workflow names what runs. The Go handler decides what it does.
- No hidden background workers. If work has to happen later, it is a workflow someone can read.
- No infrastructure the size of the framework. No scheduler, no control plane, no embedded database by default.
- No untyped dict soup between stages. Every boundary has a Go type behind it or an explicit schema.
- No vendor lock-in at the edges. Providers, transports, and stores attach as adapters. Swap one, keep the graph.
Convinced? Start with the smallest possible flow and grow from there.