Flow Schema Reference¶
The field-level reference for Event Script flow configuration files: every field the flow
compiler (crates/event-script/src/compiler.rs) accepts, and the validation it applies.
Flows are compiled and validated at application startup, so this page is how you author flows
the engine will accept. The flow YAML syntax is identical to the Java engine's — flow
files port between the two implementations unchanged.
This page is deliberately terse. The rules-at-a-glance summary is the flow grammar; the tutorial with worked examples and the full data-mapping catalog is Event Script Syntax.
The flow list (flows.yaml)¶
The engine discovers flows through one or more flow-list files, named by the
yaml.flow.automation application property (default classpath:/flows.yaml; a
comma-separated list loads several):
flows¶
| Type | Required |
|---|---|
| list | yes |
File names of the individual flow configuration files. Only .yml/.yaml names are accepted
(anything else is logged and ignored); the list is de-duplicated and loaded in sorted order.
location¶
| Type | Default |
|---|---|
| string | classpath:/flows/ |
Base location for resolving the file names. The shipped examples/hello-flow list:
Load-time failure semantics (resilient by design): an unreadable list file is a warning and that location is skipped; an invalid flow is an error and that flow is skipped; an invalid data mapping drops only the offending task while the rest of the flow loads — the runtime then fails fast if the flow ever reaches the missing task.
Flow-level fields¶
flow.id¶
| Type | Required |
|---|---|
| string | yes |
Unique identifier across all loaded flows (a duplicate id rejects the later file). Referenced
by rest.yaml (flow: binding), by sub-flow tasks as
flow://{id}, and by knowledge-graph delegation (extension=flow://{id}). The Playground's
list flows command shows each deployed id with its description.
flow.description¶
| Type | Required |
|---|---|
| string | yes |
Human-readable purpose of the flow — displayed next to the id by list flows.
flow.ttl¶
| Type | Required |
|---|---|
| duration | yes |
Maximum wall-clock lifetime of a flow instance; on expiry the instance is aborted. Written
with an s/m/h/d suffix (30s, 2m) or as bare seconds; the minimum is 1 second.
flow.exception¶
| Type | Required |
|---|---|
| string | no |
Route name of the global exception handler for this flow. See Exception handling.
first.task¶
| Type | Required |
|---|---|
| string | yes |
The task where execution starts, referenced by task name (which defaults to process).
external.state.machine¶
| Type | Required |
|---|---|
| string | conditional |
Route name (or flow://{id}) of an external state-machine service. Required whenever any
task maps to the ext: namespace — the compiler rejects the flow otherwise. See
Event Script Syntax for the ext: write/delete/append forms.
tasks¶
| Type | Required |
|---|---|
| list | yes |
The task definitions. The list must be non-empty and must contain at least one task with
execution: end, or the flow is rejected.
A minimal valid flow:
flow:
id: 'my-flow'
description: 'Example flow'
ttl: 30s
first.task: 'my.service'
tasks:
- input:
- 'input.body -> *'
process: 'my.service'
description: 'Call my service'
output:
- 'result -> output.body'
execution: end
Task fields¶
process¶
| Type | Required |
|---|---|
| string | conditional |
Route name of the composable function this task runs, or flow://{flow-id} to invoke a
sub-flow. Either process or name must be present. Any other scheme://
form is rejected, and when name is a flow:// URI, process (if given) must be a
flow:// URI too.
name¶
| Type | Default |
|---|---|
| string | value of process |
Unique task identifier within the flow — this is the name used by first.task, next,
join, and pipeline references. Required only when the same process route appears in
more than one task.
description¶
| Type | Required |
|---|---|
| string | yes |
Non-blank purpose of the task; validated at compile time.
input / output¶
| Type | Required |
|---|---|
| list | yes |
Data-mapping rules, each a 'source -> destination' string (use [] for none). A three-part
rule 'LHS -> model.var -> RHS' compiles into two rules. The namespace catalog —
input.*, model.*, error.*, constants, f: plugins, JSONPath on the left; the function
input body, output.*, model.*, file(…), ext: on the right — lives in the
flow grammar and
Event Script Syntax; this page does not
repeat it. The compiler enforces:
- exactly one usable
->per compiled rule; a source must differ from its destination; - a task with any invalid mapping is dropped (error-logged) while the flow still loads;
a rule with a dangling
.or:fails the whole flow; - the reserved state-machine keys —
model.cid,model.flow,model.instance,model.ttl,model.trace,model.none(including nested paths beneath them), andmodel.parent/model.rootas whole targets — must never be mapping destinations (writing beneathmodel.parent.*is the sub-flow shared-state mechanism and allowed); - the
decisiondestination is only valid in adecisiontask, and legacy:typeconversion suffixes still parse but are deprecated — they are auto-converted tof:plugin calls with a warning.
execution¶
| Type | Required |
|---|---|
| string | yes |
One of the eight execution types:
next¶
| Type | Required |
|---|---|
| list | conditional |
The subsequent task name(s). Required for every type except end and sink (where it is
ignored); sequential and pipeline take exactly one entry; decision needs at least two.
In a decision task the list order is the routing table — see
decision — and an entry may carry the
@retry keyword.
exception¶
| Type | Required |
|---|---|
| string | no |
Route of a task-level exception handler, overriding flow.exception for this task only.
delay¶
| Type | Required |
|---|---|
| int or string | no |
Delay before the task executes: an integer in milliseconds (an ms suffix is cosmetic and
stripped), which must be positive and less than flow.ttl — or a model.* variable
resolved at run time.
join¶
| Type | Required |
|---|---|
| string | conditional |
The task where execution resumes once all branches complete. Required when execution
is fork.
source¶
| Type | Required |
|---|---|
| string | no |
Turns a fork into a dynamic fork: a model.* path holding a list to iterate, creating
one branch per element. Requires exactly one next task; inside it,
model.<source>.ITEM is the current element and model.<source>.INDEX the zero-based index.
pipeline¶
| Type | Required |
|---|---|
| list | conditional |
Ordered list of task names to run within this task's context. Required when execution
is pipeline.
loop¶
| Type | Required |
|---|---|
| map | no |
Iteration control for a pipeline task, with two keys:
statement'for (<init>; <comparator>; <sequencer>)'— the init (model.n = 0) is optional; the comparator uses<,<=,>,>=betweenmodel.*keys and/or integers; the sequencer ismodel.n++ormodel.n--and its key must appear in the comparator. Or'while (model.key)'— a single model key, no operators or assignment.condition- One string or a list, each
'if (model.key) break'or'if (model.key) continue', evaluated before each iteration.
loop:
statement: 'for (model.n = 0; model.n < model.limit; model.n++)'
condition: 'if (model.quit) break'
pipeline:
- 'step.a'
- 'step.b'
Execution types¶
Worked examples for every type are in Event Script Syntax — task types; the compile-time shape of each is:
sequential¶
Runs the function, then passes control to exactly one next task.
decision¶
Branches on the value its output maps to decision. true (or 1) routes to the first
next entry, false (or 2) to the second; an integer N is 1-based — N routes to
the Nth entry, so a decision can fan to more than two tasks. Requires at least two next
entries and the -> decision output mapping; a value that is missing or out of range aborts
the flow with status 500.
parallel¶
Fans out to two or more next tasks concurrently, all sharing the same state machine. There
is no join — every branch runs to its own end or sink.
fork¶
Runs its next tasks concurrently and resumes at the mandatory join task once
all branches complete. Add source to iterate a model list (dynamic fork).
pipeline¶
Runs the tasks in the pipeline list sequentially within this task's context
— optionally under loop control — then passes to exactly one next task.
response¶
Sends the HTTP response immediately from its output mappings, then continues to its one
next task asynchronously — acknowledge now, keep working.
end¶
Terminates the flow; its output mappings form the final response (unless a response task
already sent one). Every flow must contain at least one end task.
sink¶
A terminal task with no outbound connection and no response — the leaf of a parallel or
fork branch.
Sub-flows¶
A task whose process is flow://{flow-id} invokes another flow as a subroutine: the task's
input mapping feeds the child's input.* dataset, the child's final response becomes the
task's result, and the child can reach the parent's state machine through
model.parent.<key> (model.root.<key> is an alias of the same object). See
Event Script Syntax — hierarchy of flows.
Exception handling¶
flow.exception names a global handler that catches any unhandled task error; a task's own
exception field overrides it for that task. A handler is an ordinary task (usually
execution: end) whose input mapping reads the error dataset:
- input:
- 'error.code -> status'
- 'error.message -> message'
process: 'v1.exception.handler'
description: 'Return a structured error response'
output:
- 'result.status -> output.status'
- 'result -> output.body'
execution: end
error.task- Name of the task that failed.
error.code- The status code of the error.
error.message- The error message (a nested sub-flow error is unwrapped to its message).
Rust port
The Java reference documents these keys as error.status and error.stack. The engine's
error dataset — here exactly as in the canonical fixture flows — uses error.code
for the status code, and the Rust engine produces no stack trace: an error.stack
mapping is tolerated but resolves null.
If the flow-level handler itself throws, the flow aborts rather than looping (exception-loop
guard). Without any handler, the flow aborts and the caller receives the error as
{type: "error", status, message} with the matching HTTP status. The built-in
simple.exception.handler can serve as a ready-made handler: it logs the error context and
echoes that same shape for the response mapping.
The built-in resilience handler¶
resilience.handler implements retry with backoff as a decision task. Wire it as a task's
exception: handler; it answers with decision 1 (retry), 2 (abort), or 3
(alternative path), so its next list is [retry-target, abort-task, alternative-task].
In the retry slot, the keyword @retry re-executes the task that failed;
'my.task | @retry' falls back to my.task when the failed task cannot be resolved.
Condensed from the canonical resilience-demo fixture (crates/event-script/tests/resources/flows/resilience-demo.yml):
- input:
- 'error.code -> status'
- 'error.message -> message'
- 'model.attempt -> attempt'
- 'int(3) -> max_attempts'
- 'int(500) -> delay'
- 'text(401, 403-404) -> alternative'
process: 'resilience.handler'
description: 'Retry, abort, or reroute'
output:
- 'result.attempt -> model.attempt'
- 'result.decision -> decision'
execution: decision
next:
- 'my.task | @retry'
- 'abort.request'
- 'alternative.task'
Its input contract (all optional except the failure context):
status/message- The failure being handled (map from
error.code/error.message). Astatusof 200 is gatekeeper mode — proceed immediately with decision 1. attempt/max_attempts- Retry bookkeeping: map
result.attemptback tomodel.attempt; when the attempt count exceedsmax_attemptsthe handler decides 2 (abort) with the original status and message. delay- Milliseconds between retries (floor 10; the first attempt is immediate).
alternative- Status codes or ranges (
'401, 403-404') that route to decision 3 instead of a retry. cumulative+backoff_trigger/backoff_seconds/backoff- Backoff control: when the cumulative failure count exceeds the trigger, the handler
decides 2 with status 503 and a
result.backofftimestamp; mapresult.cumulativeandresult.backoffto durable storage (the fixture usesfile(…)) and feed them back in.
Built-in service routes¶
| Route | Purpose |
|---|---|
no.op |
Pass-through echo function — a routing-only task (500 instances by default; tune with worker.instances.no.op). |
simple.exception.handler |
Logs the error context and returns {type, status, message}. |
resilience.handler |
Retry / abort / reroute decisions, as above. |
async.http.request |
The built-in HTTP client as a flow task. |
http.flow.adapter |
The HTTP-to-flow bridge used by rest.yaml flow binding — not called from tasks. |
See also¶
- Flow grammar — the same schema as compact rules, with the data-mapping mini-language.
- Event Script Syntax — the tutorial: worked examples for every execution type and mapping form.
- REST Automation — binding HTTP endpoints to flows.
Adapted from the mercury-composable guide docs/guides/flow-schema-reference.md; behavior
verified against this repository's source.