NEXSS FLOW · OPEN SOURCE ORCHESTRATION
Typed DSL · Go runtime · Apache-2.0 | Actions + Streams
NEXSS FLOW · ACTIONS · STREAMS · AI

Compose complex work.
Keep the flow typed.

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.

Typed
Action boundaries
Lazy
Stream execution
Standalone
Single binary deploy
Why nflow

Five problems, one graph

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.

Types where it matters

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.

Lazy streams, no plumbing

Iterate over files, HTTP chunks, LLM tokens, or NATS messages with iter.Seq2. Cancellation, backpressure, and early stop come for free. No goroutine boilerplate.

Resilience built into composition

|| for fallback. & for parallel. loop(...) until(...) for bounded retry. Circuit breakers, deduplication, and coalescing attach to atoms without a separate middleware stack.

Standalone binaries

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.

One source, every transport

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 runtime infrastructure

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.

Core principle

One graph for actions, streams, and AI.

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.

Typed state at boundaries
Streams without plumbing
Bounded retries and fallback
Readable domainless DSL

Flow building blocks Kernel + Flow

CORE CONTRACTS
AnyAction / AnyStreamAction Typed records + schemas Projection + loop state Parallel composition Fallback policies
AI COMPOSITION
model + typed output stream review workers bounded repair loops adapter-owned policy
RUNTIME EDGES
NATS · HTTP · CLI stream transports pluggable adapters observability + sandbox
Pipeline syntax

Six forms, one expression

An atom names an action; operators compose them; projections reshape values between them.

FormMeaning
A -> BPass the left result to the next action.
A | BAlias for ->.
A || BTry 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=valApply a modifier to that action (:timeout=1s, :retry=3, :auth, :name=x).
Common directives
  • @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.
Standard modifiers
  • :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.

Flow runtime

The nflow model

A readable orchestration layer over the Nexss Kernel: typed actions, streams, parallel work, structured output, and resilient execution.

Typed action graph

Compose existing actions without moving business logic into the DSL.

Inline schemas

Use :typed=Verdict or :schema=Verdict at AI boundaries and keep important outputs explicit.

Lazy streams

Parallel reviewers, merge, filter, batch, collect, and reduce with a stream boundary.

AI fallback + loops

Run bounded repair loops and switch models on classified provider failures.

The language is the graph

Less glue code.
More intent.

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.

04_data_transformation.nflowprojection · typed · asserted
@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 Nexss point of view

A language for the work between services.

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.

01 · NAME

Intent stays visible

A reader can see topology, fallback, loop boundary, and state projection without opening five service repositories.

02 · TYPE

Contracts stay real

Typed actions, typed stream items, and structured AI output keep the important boundaries explicit instead of turning the graph into untyped maps.

03 · RUN

Runtime does the hard work

Cancellation, bounded concurrency, retries, provider fallback, and stream lifecycle belong to the runtime — not to every workflow author.

Runtime stages

From DSL to execution

A small number of explicit stages keeps complex workflows debuggable and testable.

1 · Compile

Parse DSL → validate contracts
typed
  • Build a graph of typed actions and stream operators.
  • Resolve registered actions, schemas, and runtime adapters.
  • Keep AI providers behind explicit action contracts.
  • Leave domain-specific behavior to domain packages.

2 · Execute

Kernel + Flow runtime
parallel
  • Run branches, streams, projections, and loops with bounded concurrency.
  • Propagate context, cancellation, timeout, and structured errors.

3 · Observe

Events + metrics
visible
  • Observe action and stream lifecycle without changing business handlers.
  • Measure throughput, failures, retries, and provider fallback.
  • Keep per-item observation opt-in.

4 · Extend

Adapters, not rewrites
open
  • Add AI, NATS, HTTP, sandbox, and storage adapters at the edges.
Getting started

From zero to a running flow
in five minutes.

No cluster. No scheduler. No sidecar. Install the CLI, write a .nflow, and run it. Add transports, AI, and resilience when you need them.

terminal install · lint · run · build
# 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 CLI
Practical patterns

Runnable patterns from the examples

Each pattern below is a real checked-in .nflow file. Lint it, run it, assert on its result.

Pattern 01 · Timeout & Fallback

Per-action timeout with local fallback

: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." }
02_sleep_and_timeout.nflow →
Pattern 02 · Error Fallback

Recover from a failed action

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" }
05_error_fallback.nflow →
Pattern 03 · Parallel Actions

Concurrent branches with deterministic keys

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"
07_parallel_actions.nflow →
Pattern 04 · Error Routing

Match error kinds with @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" }
06_error_handling_and_retry.nflow →

Run the full curated example set locally with task examples, or an individual flow with nflow run <file.nflow>.

Start with the graph

Make the workflow explicit.

Start with one typed action. Add a stream when the work becomes incremental. Compose it in Flow when the graph becomes the product.

Kernel + Flow · Apache-2.0 · Open source