Concepts
The Metolia Workflow Language (MWL) describes workflows as JSON documents: directed graphs of named Steps that call services, branch, run work concurrently, wait, and handle failures.
This section introduces the model: the smallest complete workflow, the execution loop, and the concepts the rest of the reference specifies.
A minimal Flow
A workflow definition is a JSON document whose root object is a Flow. The following definition is complete and runnable: a Flow of two Steps, one that calls an HTTP service and one that ends the workflow.
{
"$schema": "https://mwl.dev/v0.1/flow/schema.json",
"entrypoint": "greet",
"steps": {
"greet": {
"action": "Call",
"call": {
"provider": "mwl:provider.call/example/http/v1",
"with": { "method": "GET", "path": "/hello" }
},
"next": "done"
},
"done": { "action": "Return" }
}
}Execution enters at the Step named by entrypoint. Here greet runs first: it
dispatches its Call to an HTTP provider and, on success, follows its next to
done. done is terminal: Return completes the Flow, which produces a Result
recording how it ended. That is the whole loop: run the current Step; if it
transitions, follow next and repeat; if it is terminal, the Flow completes
with its Result.
Flows and frames
A Flow is the unit of definition: an entrypoint, a map of steps, and
optionally named subflows (flows), parameters, and middleware. The same Flow
object describes a whole workflow or a piece of one that a larger Flow composes.
When a Flow runs, it runs inside a frame, an execution-time instantiation of the
Flow with its own variables and lifecycle; one Flow definition may be running as
many frames at once. A frame evaluates one thing at a time: all concurrency is
between the target executions a Step has outstanding — the subflow frames and
provider executions its Calls run. The Flow object and its fields are specified
in The Flow object; the frame lifecycle in
Execution model.
Steps and actions
A Step is a named node in a Flow’s graph. Each Step does one thing, named by its
action: Call dispatches a Call, Gather runs many Calls concurrently,
Match branches, Pass reshapes data without calling anything, Sleep waits,
and Return and Raise end the Flow. Around the action, Steps share a small
uniform field set for shaping data, capturing variables, and routing. The shared
fields and the Step lifecycle are specified in
Steps and step mechanics; each action, with its complete
field set, in Step actions.
Routing and terminal Steps
A Step either transitions or terminates. A transitioning Step names its
successor in next, and control passes there on success; failures on Steps that
dispatch Calls route separately, through catch clauses — the success fields
and catch forming the two arms of a match on the Step’s Result. A terminal
Step ends the Flow: Return completes it successfully, and Raise completes it
with a failure. Routing, catch, and the terminal mechanics are specified in
Steps and step mechanics.
Calls and Results
A Call is the dispatch unit: it names a target, a provider or a Flow, gives it
arguments (with) and a data payload (input), and yields a Result. A Result
is a discriminated value recording one outcome: a success carrying a value, or
a non-success carrying a structured failure envelope. Every Call produces
exactly one Result, and so does every frame, which is what lets a Flow be called
like any provider. The call object and the Result are specified in
The Call interface and Result.
The data plane
A Flow moves data between its Steps. When a Flow runs, its caller supplies the
frame’s input; the entry Step receives a value, does its work, and emits an
output. Each transition hands one Step’s output to its successor as input, and
the value a terminal Return emits is what the Flow’s success Result carries.
The values that move this way—Step inputs and outputs, the value a Result
carries, what an expression evaluates to—are collectively the data plane.
Configuration travels beside the data plane, not through it, on
the control plane: arguments supplied as with are
validated against a target’s declared parameters, while the data payload flows
on input.
The data plane carries JSON values, typed according to the rules specified in
The data model. A Step’s input and output shaping is
specified in Steps and step mechanics, and the Result and
the with/input channels in
The Call interface and Result. How data moves end to end,
across Steps, middleware, and subflows, is synthesized in
Data flow.
The control plane
Beside the data plane sits the state that steers a workflow rather than flowing
through it: the control plane. Configuration enters through a target’s
declared parameters, supplied as with arguments at the call site; it seeds
the frame’s variable namespace, vars, which a Step’s assign writes as
execution proceeds. A subflow never shares its caller’s variables; values cross
the boundary only explicitly. The engine contributes execution state: frame and
Step metadata and the failure context, exposed to expressions as the execution
context. The variable model is specified in The Flow object;
assign timing in Steps and step mechanics; the execution
context in Execution context.
Expressions
Definitions stay declarative by embedding expressions where values need
computing: a string field consisting of exactly one {{ ... }} expression
evaluates to the expression’s typed result. Expressions read a small set of
bindings, such as vars and step, that expose the running workflow’s state,
and every expression produces a data-model value. The embedding, the binding
roots, and the expression-language profile are specified in
Expressions.
Providers
A provider is a platform-supplied capability addressed by URI; providers are
MWL’s extension surface. A Call’s target may be a provider
(mwl:provider.call/example/http/v1), and middleware are providers too
(mwl:provider.middleware/mwl/retry/v1): the language defines the shapes, and
providers supply the behavior. A provider declares the parameters schema its
arguments are validated against and a catalog of the failure codes it can
produce. Providers, their URI namespacing, and their catalogs are specified in
Providers.
Middleware
Middleware wraps work without changing it: an ordered stack of middleware
providers around a Call Step’s dispatch, or around a whole Flow’s Step graph,
acting on entry, on success, on failure, or always. Cross-cutting behavior such
as retry, caching, and notification lives here rather than in the graph itself.
The phase model and composition are specified in
Middleware mechanics; the middleware catalog in
Providers.