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
}
// ↓ explicit dependency declaration
stage execute after plan -> ChangeResult[] {
// plan is visible here
return parallel map plan.work as item { /* ... */ }
}
stage review after execute -> ReviewResult { /* ... */ }
return { changes: execute, review: review } // last statement must be return
}
The three dependency rules
| Rule | Diagnostic |
|---|---|
| A dependency must already be declared | unknown-stage |
| Dependencies must precede in source order (no forward deps) | forward-dependency |
| A stage body may only read results of declared dependencies — even if an earlier stage already completed | undeclared-dependency |
Dependencies are a whitelist: with stage execute after plan, the body sees
plan and nothing else. Every stage's input set is fully auditable from its
signature.
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)
V1 executes stages in source order; dependency metadata flows into the
Flow IR (StageIR.dependencies) for a future DAG scheduler — declaring
dependencies completely is leaving information for that scheduler.
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 after audit -> 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