Top-level structure & pipelines
A .flow file contains one or more workflows. This page covers one
workflow's skeleton: declaration, resources, limits, types, pipeline and
stages.
The workflow declaration
workflow deliver_change(input: ChangeRequest) -> Delivery {
// use / limits / type / pipeline, any order
pipeline delivery {
return { changes: [], review: { approved: true } }
}
}
- The parameter must be named
input, and there is exactly one (workflow-param-name) - The output type follows
->: the pipeline's return value is checked at compile time (workflow-return-mismatch), then again against the Schema at runtime - At most one
limitsand onepipeline; thepipelineis required
use: declare resources
use team "engineering-team" as engineering
use agent "secure-code-reviewer" as security
use declares logical resources — the string is a resource id pointing
at no concrete product. Resolution happens at runtime
(AgentRuntime.resolveAgent/resolveTeam); aliases are workflow-unique.
limits: budgets
limits {
concurrency: 4 // max concurrent agent calls (positive integer)
agent_runs: 12 // max total agent invocations for the workflow
duration: 30m // wall-clock cap (duration literal)
}
All three fields are optional; the effective value is the smaller of host
policy ∩ workflow limits. Exceeding agent_runs raises
LimitExceededError; exceeding duration raises WorkflowTimeoutError.
type: structured types
type WorkItem {
id: text
owner: member<engineering>
writes: path[]
acceptance: text[]
}
type Plan {
summary: text
work: WorkItem[1..6] // array, 1 to 6 items
}
Declarations may appear in any order with forward references allowed; recursion is not. Full rules in Type system.
pipeline and stages
The pipeline is a statement container; a stage is a named phase whose name is its result variable:
pipeline delivery {
stage plan -> Plan {
let result = agent(engineering.main) { /* ... */ expect Plan }
require result.work.count in 1..6 else fail "..."
return result
}
// stages execute in source order; the earlier plan is visible here
stage execute -> ChangeResult[] {
// plan is visible here
return parallel map plan.work as item { /* ... */ }
}
stage review -> ReviewResult { /* ... */ }
return { changes: execute, review: review } // last statement must be return
}
Stage visibility rules
| Rule | Result |
|---|---|
| Stages execute in source order | Each stage completes before the next starts |
| A stage can read results from earlier stages | Results are bound by stage name |
| Reading a later or missing stage | unknown-variable |
stage execute can read the completed plan, but cannot see review, which
appears later in source. The language has no separate dependency whitelist.
Return placement
- The pipeline's last statement must be
return(missing-return), andreturnmay only appear at the end (pipeline-return-position) - Same for stages (
stage-missing-return) - A stage's return value must be assignable to the declared
-> TypeRef(stage-return-mismatch)
Stages execute in source order. There is no separate dependency metadata or DAG scheduling model.
Statements in pipeline / stage bodies
| Statement | Grammar | Constraint |
|---|---|---|
let | let name = expr | binds in current scope; duplicate names → duplicate-variable |
require | require bool else fail "reason" | condition must be bool; failure raises WorkflowAssertionError |
emit | emit progress { ... } | progress events only, object-literal payload |
if | if bool { ... } else { ... } | condition must be bool; then/else are block scopes |
return | return expr | placement as above |
Stages cannot nest stages; pipelines cannot contain bare expressions.
Pattern: conditional early return
Inside a stage, combine an if-return with a trailing fallback:
stage route -> text {
if audit.needs_manual_review {
return agent(approver) { task "..." expect text }
} else {
emit progress { decision: "direct" }
}
return agent(preparer) { task "..." expect text } // fallback
}
Next steps
- Type system
- agent() calls — the only point of non-determinism