Nexss Flow is a Go runtime and language for composing .nflow programs from registered actions. The nflow CLI runs flows, lints them, builds standalone binaries, and inspects the active compiler surface.
One DSL. One binary. Built on the Nexss Kernel — no runtime infrastructure required.
Modern systems orchestrate typed actions, streams, parallel work, AI calls, and fallback policies. Today that means five different tools. Nexss Flow names all of them in one language.
Actions stay typed in Go (action.New[Req, Res]). The DSL composes them without rewriting business logic. You get IDE navigation, compile-time checks, and Go-level performance at every atom.
Iterate over files, HTTP chunks, LLM tokens, or NATS messages with iter.Seq2. Cancellation, backpressure, and early stop come for free. No goroutine boilerplate.
|| for fallback. & for parallel. loop(...) until(...) for bounded retry. Circuit breakers, deduplication, and coalescing attach to atoms without a separate middleware stack.
nexssflow build app.nflow -o app produces a single executable. The DSL, every declared transport, and the pipeline source are embedded. Deploy with FROM scratch.
The same action runs over HTTP, NATS, CLI, cron, worker, or in-process bus. Transports attach at the edge, not inside the business handler. Swap one, keep everything else.
No scheduler daemon. No external workflow database. No sidecar. The binary is the runtime. Copy it, run it, it works. Add a database only when your business logic needs one.
Flow keeps orchestration visible: the graph is the documentation, and the runtime owns execution semantics.
Keep domain models in their own packages. Connect them with explicit typed contracts. Compose them in a language short enough to review as a diagram and precise enough to execute.
An atom names an action; operators compose them; projections reshape values between them.
| Form | Meaning |
|---|---|
A -> B | Pass the left result to the next action. |
A | B | Alias for ->. |
A || B | Try the right expression if the left expression fails. |
(A & B) | Run the grouped branches in parallel. Results keyed by branch action name; repeats get name#1, name#2 in source order. |
{ key: expr } | Project fields and expressions into a new object using expr-lang. |
action @{ k: v } | Pass named values as the action's arguments. |
action:mod=val | Apply a modifier to that action (:timeout=1s, :retry=3, :auth, :name=x). |
@description "..." — attach a human-readable description.@assert: expression — check the final result after the flow runs.@pipeline name … @end — declare a named pipeline.@include "path.nflow" — inline another Flow file at this position.@require ... — request an extension or library bundle.@on_error { ... } — route failures by error.kind.@config:strict =true — runtime request validation via validation.Struct.:timeout=2s — per-action wall-clock deadline.:retry=3 — retry up to N times with exponential jitter backoff.:cache=30s — read-through cache with singleflight stampede protection.:auth — require authenticated context (xerr.KindUnauthorized).:role=admin — enforce RBAC claim.:name=mykey — override the gather key for parallel branches.:tag="hot,fast" — metadata tags for catalog and observability.:strict / :lenient — opt in/out of runtime validation per atom.Directive and modifier availability depends on the bundles loaded by a runner. Use nflow list to inspect the active catalog rather than relying on a fixed feature list.
A readable orchestration layer over the Nexss Kernel: typed actions, streams, parallel work, structured output, and resilient execution.
Compose existing actions without moving business logic into the DSL.
Use :typed=Verdict or :schema=Verdict at AI boundaries and keep important outputs explicit.
Parallel reviewers, merge, filter, batch, collect, and reduce with a stream boundary.
Run bounded repair loops and switch models on classified provider failures.
nflow does not replace your Go services. It names the composition: what runs, what can run in parallel, what is typed, and what happens when an external model fails.
The hard parts stay in the Kernel and adapters. The workflow stays short enough to review as a diagram and precise enough to execute.
@description "Shape Shifting: Deep JSON Projection and Data Mapping"
@assert: result.summary == "Julia (ID: 101) is active"
{
user: { id: 101, name: "Julia", metadata: { status: "active" } },
system_flag: true
}
# Project and transform fields with expr-lang expressions, including string concat.
-> {
id: .user.id,
name: .user.name,
status: .user.metadata.status,
summary: .user.name + " (ID: " + string(.user.id) + ") is " + .user.metadata.status
}Real example from examples/00_flow_basics/04_data_transformation.nflow. Run with nflow run examples/00_flow_basics/04_data_transformation.nflow.
The unique part is not another syntax layer. It is the shared contract between Kernel actions, lazy streams, AI outputs, and the graph that connects them.
A reader can see topology, fallback, loop boundary, and state projection without opening five service repositories.
Typed actions, typed stream items, and structured AI output keep the important boundaries explicit instead of turning the graph into untyped maps.
Cancellation, bounded concurrency, retries, provider fallback, and stream lifecycle belong to the runtime — not to every workflow author.
A small number of explicit stages keeps complex workflows debuggable and testable.
No cluster. No scheduler. No sidecar. Install the CLI, write a .nflow, and run it. Add transports, AI, and resilience when you need them.
# 1. Install the CLI (requires Go 1.26+)
go install github.com/nexssp/flow/cmd/nflow@main
nflow version
# 2. Lint the canonical example (no execution)
nflow lint examples/00_flow_basics/04_data_transformation.nflow
nflow lint ./examples/... # recursive discovery
# 3. Run a flow (post-run assertion optional)
nflow run examples/00_flow_basics/04_data_transformation.nflow
nflow run examples/00_flow_basics/04_data_transformation.nflow \
--assert='result.id == 101'
# 4. Build a standalone binary (host platform only)
nflow build -o ./bin/my-flow \
examples/00_flow_basics/04_data_transformation.nflow
./bin/my-flow # runs without the CLIEach pattern below is a real checked-in .nflow file. Lint it, run it, assert on its result.
:timeout=1s sets a deadline for one action. If the action returns an error (including a timeout), || tries the local fallback. Not a workflow-wide SLA or cost cap.
{ query: "SELECT * FROM huge_table" }
-> sleep:timeout=1s @{ duration_ms: 5000 }
|| { recovered: true, error: "Database took too long, serving fallback data." }The || operator runs the right side only if the left side fails. The kernel classifies the error (timeout, rate-limit, network, 5xx); 4xx errors are real errors, not triggers to retry.
fail @{ kind: "Validation", message: "User email is invalid" }
|| const @{ value: "Recovered" }The & operator fans out branches that run concurrently. Repeated action names get name#1, name#2 in source order — so downstream projections can address each one deterministically.
( const @{ value: "Data A" } & noop & const @{ value: "Data B" } )
# result["const#1"] == "Data A"
# result["const#2"] == "Data B"
# result.noop.request == "local input"@on_error@on_error routes failures to an action by matching error.kind. Combine with retry on the same atom to keep transient failures out of the catch block.
@on_error {
when error.kind == "Timeout" -> error.info
when error.kind == "Validation" -> error.info
else -> error.info
}
fail @{ kind: "Validation", message: "User email is invalid" }Run the full curated example set locally with task examples, or an individual flow with nflow run <file.nflow>.
Start with one typed action. Add a stream when the work becomes incremental. Compose it in Flow when the graph becomes the product.