Non-goals
MWL is deliberately scoped. The following are design directions that have been considered and rejected. Future revisions should preserve these decisions rather than relitigate them.
Event-driven triggers and invocation
MWL does not define how workflow executions are initiated. The language describes what to do given input; it has no constructs for HTTP endpoints, scheduled triggers, queue subscriptions, file-arrival hooks, or any other mechanism by which inputs reach a workflow. These are platform concerns.
The reasoning mirrors the Orchestration is separate from computation principle: the runtime orchestrates a Flow given an input, and what produces that input is outside the Flow’s definition. A platform may surface workflow invocation through any number of front-end mechanisms; the workflow doesn’t know and shouldn’t need to know.
This keeps the language tightly scoped and avoids the problem of every platform’s trigger model leaking into the language definition. Authors who want to model “this workflow runs every five minutes” or “this workflow runs when an object lands in a bucket” handle that at the platform layer, not in the workflow definition.
Reuse as a language goal
MWL does not pursue reuse as a design goal. The language does contain a
reuse-shaped feature: a Flow declared once in a
flows map can be called from many Steps and
from Gather dispatches. But that feature was not the product of a reuse
requirement. It follows from the unified Call interface: once a Flow and a
provider yield the same kind of Result through the same call shape
(Flow-Call Result parity),
letting a call target a named Flow costs the language almost nothing, and the
flows map fell out of the unification. The win is taken; the goal is
unchanged.
The boundary is the definition document. Flow references resolve lexically
within the document that contains them: a call may target an entry in its own
Flow’s flows map or in the map of any enclosing Flow
(Flow-name scoping). There are no
imports, no registry references, no fragment inclusion, and no mechanism by
which one definition names a Flow declared in another. A definition is
self-contained, and its behavior is a function of the document alone — the same
property the scoping rules and the vars model protect at runtime.
Authors who want reuse beyond that boundary — shared Step libraries, parameterized templates, organization-wide subflows — should produce MWL definitions through higher-layer tooling: programmatic SDKs, templating systems, or composition libraries that compile into self-contained MWL documents. Reuse expressed through duplication at compile time is acceptable; workflow definitions are not typically large enough for duplication to be a real cost, and the runtime gains from self-contained definitions (simpler validation, simpler execution, simpler reasoning) are tangible.
Reuse should not be confused with composability, which has always been a goal
and is sought throughout the specification. Composable interfaces are often
simpler and yet more expressive, and thus more capable: one call shape consumed
by both Call and Gather, middleware entries that stack in author-chosen
order at two attachment levels, Flows that nest because every Flow presents the
same Result contract. The non-goal here is narrow — the language does not chase
mechanisms for sharing definitions — and it implies nothing against the
composition of the constructs the language already has.
Cross-definition invocation
Within one definition, calling a Flow is first-class. Across definitions, the language has no concept at all: no action, reference form, or primitive for starting another workflow execution. If a platform offers a provider that starts other workflow executions, that is a provider concern; workflows targeting it are dispatching to an external integration, not invoking anything the language models. Flow-Call Result parity intentionally smooths that path: because every completed Flow yields an ordinary Result, a platform’s “run a workflow” provider can present to its caller exactly like any other call target.
Data typing across the Step boundary
MWL does not enforce schemas or type contracts on the data flowing between
Steps. The spec defines the shape of the execution context (step.input, a
Result’s value, the failure envelope) but treats the content of those fields
as opaque. Inter-Step data is whatever the previous Step produced; the language
does not validate it against a declared type before delivering it to the next
Step.
This is not a statement that typing and schema enforcement lack value; to the
contrary, they are extremely valuable. The position is that such enforcement
belongs at a higher layer: one that has insight into what specific providers
accept and return, what shape a given Call Step’s output will take, and what
downstream Steps expect. Tooling above the language such as SDKs, registries,
IDE integrations, linters, etc. can provide typed contracts with richer
knowledge than the language definition alone possesses. The language defines
data flow mechanics; data flow validation is a tooling concern.
The one place the language does validate values is the control plane: a call’s
with is validated against its target’s declared parameters schema, because
configuration is a contract the target itself declares
(The three axes).
The data channel stays open by design.
Versioning within the language
A workflow definition carries no version field of its own beyond the spec URI in
$schema, which is not the workflow definition’s version but the version of MWL
in which it is authored. A definition cannot and does not know its version:
versioning is a higher-level concern, something the platform should manage
through content hashing, monotonic identifiers, or whatever mechanism the
platform chooses. The language is deliberately uninvolved: it describes what to
do, not which revision of what-to-do this is.
This avoids a class of problems that arise when definitions carry their own version metadata — staleness, conflicts between the declared version and the actual content, and the question of what “version” even means when definitions are generated by tooling rather than hand-edited. The normative statement of this position lives in the reference (Definition versioning); this entry records that it is a deliberate boundary, not an omission.
A required provider catalog
The specification requires a small, fixed implementation floor: the four
spec-defined middlewares (Retry, Timeout, Loop, Finally) and one call
provider, the mock.
Beyond that floor, requiring providers is a non-goal. The spec defines the
mechanism by which platforms declare and validate providers, but it does not
ship a standard catalog of common integrations (HTTP, container execution, queue
dispatch, etc.), and it does not intend to grow one as a requirement.
The reasoning: implementations should not be forced to support an integration that makes no sense in their environment, even at some cost to out-of-the-box interoperability. A workflow’s portability is a function of provider adoption, and that adoption is better driven by published provider specifications that platforms opt into than by spec mandate.
The mock provider is the sole concession, and it is admitted for two reasons.
First, it allows example workflows that are executable on any conformant
implementation, with no real integrations configured. Second, because those
example workflows can exercise every facet of the language itself, they double
as conformance tests: a corpus of mock-only workflows can validate an
implementation’s correctness end to end. A capability that exists to test the
language, rather than to integrate with anything, is the one provider that
belongs to the language.
As implementations mature and consensus consolidates around specific integrations, the spec may onboard provider specifications as recommended extensions. The base spec is unlikely to ever require more than it does today.
Vocabulary expansion and syntactic sugar
MWL aims to adopt the simplest abstractions that are still powerful enough to cover the full space of workflow control flow — and then stop. The language resists both directions of complexity: accumulating specialized constructs for patterns already expressible with existing primitives, and introducing fewer but more complex abstractions that would be harder to learn and reason about. The goal is the sweet spot: a small set of primitives, each individually simple, each broadly applicable.
The current vocabulary reflects this. Match handles conditional routing. The
Loop middleware
handles iterative re-execution with structured termination; for simple cases,
next targeting a previously-executed Step also expresses looping. Gather
handles fan-out and collection in its two forms. These are general-purpose
primitives whose composition covers patterns that other languages address with
dedicated syntax — if/else, while, for-each, try/catch. MWL does not
introduce those constructs because the existing primitives already express them,
and each additional construct would widen the vocabulary without widening the
capability. It would also introduce ambiguity about which construct to use for a
given pattern — a cost that falls on every workflow author and every tool that
processes MWL definitions.
New primitives should earn their place by enabling something genuinely inexpressible with the current set, not by being more convenient for a specific pattern. Convenience is the domain of higher-layer tooling — SDKs, builders, visual editors — that can present ergonomic interfaces while emitting the constrained MWL form.