Skip to main content

Language reference

The complete reference for the Agent Flow language. Authoritative implementation: packages/lang/src/language/agent-flow.langium (grammar) and packages/compiler/src/checker.ts (static semantics).

Lexis

Comments and whitespace

  • Line comments // ...; block comments /* ... */ (multiline, no nesting)
  • Whitespace and comments are hidden tokens; newlines are not significant

Identifiers

ID = /[_a-zA-Z][\w$]*/

Strings

FormNotes
"..."single-line; escapes \" \\ \n \t \r; other \x reads as x
"""..."""multiline; content verbatim; used for task; only leading/trailing newlines trimmed

Numbers and durations

TokenRegexNotes
NUMBER/\d+(\.\d+)?/decimal; no negative literals
DURATION/\d+(ms|s|m|h)(?![a-zA-Z0-9_])/500ms 5s 10m 2h; normalized to ms

Separator conventions

StructureCommas
Arrays, tools, dependencies, call args, retry codesrequired; trailing where applicable
Object/type/limits/parallel fieldsrequired; trailing allowed
workflow/stage/agent/verify statementsno commas

Types

TypeRef := PrimaryTypeRef Suffix*
PrimaryTypeRef := 'member' '<' Id '>' | Id
Suffix := '[' RangeBounds? ']'
RangeBounds := NUMBER '..' NUMBER

Repeated suffixes express nested arrays (path[][]); optional fields use field?: Type. Reading one yields internal T?; use value ?? fallback to resolve absence, with null as the explicit empty value.

Type kinds, resolution order, assignability and the JSON Schema mapping: see the full tables on the Type system page.

Top-level declarations

Workflow := 'workflow' ID '(' 'input' ':' TypeRef (',' 'context' ':' TypeRef)? ')' '->' TypeRef '{' Member* '}'
UseStatement := 'use' ('agent' | 'team') STRING 'as' ID
LimitsBlock := 'limits' '{' LimitField+ '}'
LimitField := ('concurrency' | 'agent_runs' | 'duration') ':' (NUMBER | DURATION)
TypeDeclaration := 'type' ID '{' (FieldName '?'? ':' TypeRef (',' ...)* ','?)? '}'

Pipelines and stages

Pipeline := 'pipeline' ID? '{' PipelineStatement* '}'
Stage := 'stage' ID ('after' ID (',' ID)*)? '->' TypeRef '{' StageStatement* '}'
  • The pipeline's top-level last statement must be return; typed early returns are allowed inside if
  • A stage's name is its result variable; dependencies must be declared and earlier in source order; bodies read only declared dependencies
  • V1 executes in source order; dependencies flow into the IR for DAG scheduling

Statements

StatementGrammarConstraint
letlet ID = exprno duplicates in scope
requirerequire expr else fail STRINGcondition must be bool
emitemit progress objectLiteralprogress events only
returnreturn exprtype must match the declaration
ifif expr { stmts } (else { stmts })?condition must be bool; block scopes

Expressions

Precedence (tightest first)

postfix .member [*]projection f(args)
comparison == != < <= > >= / in a..b (non-chainable)
not not (scopes over the whole comparison level)
and and (left-assoc)
or or (left-assoc)
coalesce ?? (right-assoc, loosest)

not a == b parses as not (a == b); parentheses group explicitly. optional ?? fallback returns a compatible non-optional type.

Literals and basic forms

  • Strings / multiline strings / numbers / booleans / durations
  • Array literals [e1, e2] (commas required; infer a compatible common element type)
  • Object literals { f1, f2: e2 }: explicit name: expr fields; name shorthand equals name: name with name a visible variable
  • Member access expr.member:
    • object types → field type (missing → invalid-member-access)
    • arrays → only .count (number), .empty / .any (bool)
    • Verification.passed / .failed / .checks
    • external types / context (any) → any member, result any
    • team bindings → .main (agent-target)
    • Dangerous member names constructor prototype __proto__ __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__ hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf — always rejected (dangerous-member-access)
  • Projections xs[*].field: xs must be an array; equivalent to xs.map(x => x.field); result is the field type's array; a bare projection xs[*] is invalid
  • Calls f(args): builtins only, plus team.member(...) (legal only in an agent() target position)

Builtins

FunctionParameterResultSemantics
unique(array)an arrayboolelements (stringified) have no duplicates
disjoint(arrayOfArrays)arrays of arraysboolsubarrays pairwise disjoint
union(arrayOfArrays)arrays of arraysarrayconcatenates all subarrays in order

range and in

  • a .. b: both sides numeric/duration; only legal as the right operand of in
  • x in a..b: closed interval, x >= a && x <= b

agent() calls

AgentExpression := 'agent' '(' Expression ')' '{' AgentOption* '}'
AgentOption :=
'task' TaskString # required, unique
| 'input' ObjectLiteral
| 'tools' 'none' | '[' (','? Id)* ']'
| 'write' Expression # must be path[]
| 'expect' TypeRef # required, unique; no Verification/external types
| 'timeout' DURATION # positive
| 'retry' '{' 'attempts' ':' NUMBER ('on' ':' '[' Code (',' Code)* ']')? '}'
Code := 'timeout' | 'rate_limit' | 'transient'

Three target forms: alias / team.main / team.member(expr). attempts counts extra tries; on defaults to ['transient']; decisions are by error code.

parallel / parallel map / verify

ParallelExpression := 'parallel' '{' (','? ID '=' Expression)+ '}'
ParallelMapExpression := 'parallel' 'map' Expression 'as' ID ('limit' NUMBER)? '{' Expression '}'
VerifyExpression := 'verify' '{' (','? VerifyCheck)* 'pass' 'when' Expression '}'
VerifyCheck := 'check' ID '=' Expression | 'parallel' '{' (','? 'check' ID '=' Expression)+ '}'
  • parallel: concurrent branches, barrier semantics; result {branchName: type}
  • parallel map: source must be an array; limit a positive integer; result order matches input order; typing T[] → U[]
  • verify: check names unique; initializers evaluate in the outer scope, pass when in the verify scope; result Verification<{checks...}>

Scopes

workflow : input, context, use aliases
pipeline : + completed stage results (source order)
stage : + declared dependencies
block : if then/else
map : loop variable
verify : check names (pass when only)

Reserved words and soft field keywords

The following are reserved in variable, stage, type, resource alias, parallel/check/map, and tool-name positions:

agent agent_runs and as attempts
check concurrency else emit expect
fail false if in let
limit limits map none not
on or parallel pass pipeline
progress rate_limit require return retry
stage task team timeout tools
transient true type use verify
when write workflow

They are soft keywords in type fields, object fields, and .field member access, so type Event { type: text, retry?: bool } is legal. input, context, duration, and member are also accepted as ordinary value identifiers. This keeps external JSON field names free while protecting names that enter generated-code namespaces.

JS-style while, for, function, class are not reserved — but V1 has no such statements, so they parse as errors anyway.

Frequent collisions

Position
map loop variableas task / as check / as mapitem / job / slot
check namecheck task = ...tests / security / policy
parallel branchtools = ...frontend = ...
explicit object field{ type: value }legal
let/shorthandlet expect = ... / { expect }avoid keywords

Appendix: EBNF grammar

grammar AgentFlow

hidden terminal WS: /\s+/;
hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;
hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//;
terminal STRING: /"(\\.|[^"\\\n\r])*"/;
terminal ML_STRING: /"""[\s\S]*?"""/;
terminal DURATION: /\d+(ms|s|m|h)(?![a-zA-Z0-9_])/;
terminal NUMBER: /\d+(\.\d+)?/;
terminal ID: /[_a-zA-Z][\w$]*/;

Id returns string: ID | 'input' | 'context' | 'duration' | 'member';
FieldName returns string:
Id | 'workflow' | 'use' | 'type' | 'pipeline' | 'stage' | 'agent' | 'parallel'
| 'verify' | 'require' | 'emit' | 'return' | 'if' | 'else' | 'let' | 'not'
| 'and' | 'or' | 'in' | 'true' | 'false' | 'task' | 'tools' | 'write'
| 'expect' | 'timeout' | 'retry' | 'pass' | 'when' | 'check' | 'map' | 'as'
| 'after' | 'limit' | 'limits' | 'team' | 'none' | 'on' | 'attempts'
| 'progress' | 'fail' | 'concurrency' | 'agent_runs' | 'rate_limit'
| 'transient' | 'null';
TaskString returns string: STRING | ML_STRING;
BoolKeyword returns string: 'true' | 'false';
LimitValue returns string: NUMBER | DURATION;
RetryErrorCode returns string: 'timeout' | 'rate_limit' | 'transient';

entry FlowFile: elements+=Workflow+;

Workflow:
'workflow' name=ID '(' param=WorkflowParam (',' contextParam=WorkflowContextParam)? ')' '->' outputType=TypeRef
'{' members+=WorkflowMember* '}' ;

WorkflowParam: name=Id ':' type=TypeRef ;
WorkflowContextParam: 'context' ':' type=TypeRef ;

TypeRef:
({infer MemberTypeRef} 'member' '<' team=Id '>' | {infer NamedTypeRef} name=Id)
suffixes+=TypeSuffix* ;
TypeSuffix: '[' bounds=RangeBounds? ']' ;
RangeBounds: min=NUMBER '..' max=NUMBER ;

TypeDeclaration: 'type' name=ID '{' (fields+=TypeField (',' fields+=TypeField)* ','?)? '}' ;
TypeField: name=FieldName (optional?='?')? ':' type=TypeRef ;

UseStatement: 'use' kind=('agent' | 'team') resourceId=STRING 'as' name=ID ;
LimitsBlock: 'limits' '{' fields+=LimitField (',' fields+=LimitField)* ','? '}' ;
LimitField: name=('concurrency' | 'agent_runs' | 'duration') ':' value=LimitValue ;

Pipeline: 'pipeline' name=ID? '{' body+=PipelineStatement* '}' ;

Stage:
'stage' name=ID
('after' dependencies+=ID (',' dependencies+=ID)*)?
'->' returnType=TypeRef
'{' body+=StageStatement* '}' ;

LetStatement: 'let' name=Id '=' value=Expression ;
RequireStatement: 'require' condition=Expression 'else' 'fail' message=STRING ;
ReturnStatement: 'return' value=Expression ;
EmitStatement: 'emit' 'progress' payload=ObjectLiteral ;
IfStatement: 'if' condition=Expression '{' thenBody+=StageStatement+ '}' ('else' '{' elseBody+=StageStatement+ '}')? ;

Expression: CoalesceExpression ;

CoalesceExpression infers Expression:
OrExpression ({infer BinaryExpression.left=current} operator='??' right=CoalesceExpression)? ;

OrExpression infers Expression:
AndExpression ({infer BinaryExpression.left=current} operator='or' right=AndExpression)* ;
AndExpression infers Expression:
NotExpression ({infer BinaryExpression.left=current} operator='and' right=NotExpression)* ;
NotExpression infers Expression:
{infer UnaryExpression} 'not' operand=NotExpression
| ComparisonExpression ;
ComparisonExpression infers Expression:
PostfixExpression (
{infer BinaryExpression.left=current} operator=('==' | '!=' | '<' | '<=' | '>' | '>=') right=PostfixExpression
| {infer BinaryExpression.left=current} operator='in' right=RangeExpression
)? ;
RangeExpression infers Expression:
{infer RangeExpression} start=PostfixExpression '..' end=PostfixExpression ;
PostfixExpression infers Expression:
PrimaryExpression (
{infer MemberAccess.target=current} '.' member=FieldName
| {infer Projection.target=current} '[*]'
| {infer CallExpression.target=current} '(' (args+=Expression (',' args+=Expression)*)? ')'
)* ;
PrimaryExpression infers Expression:
AgentExpression
| ParallelExpression
| ParallelMapExpression
| VerifyExpression
| {infer VariableRef} name=Id
| {infer StringLiteral} value=STRING
| {infer MLStringLiteral} value=ML_STRING
| {infer NumberLiteral} value=NUMBER
| {infer BoolLiteral} value=BoolKeyword
| {infer NullLiteral} 'null'
| {infer DurationLiteral} value=DURATION
| ArrayLiteral
| ObjectLiteral
| '(' Expression ')'
;
ArrayLiteral infers Expression: {infer ArrayLiteral} '[' (elements+=Expression (',' elements+=Expression)*)? ']' ;
ObjectLiteral infers Expression: {infer ObjectLiteral} '{' (fields+=ObjectField (',' fields+=ObjectField)* ','?)? '}' ;
ObjectField: name=FieldName (':' value=Expression)? ;

AgentExpression infers Expression: {infer AgentExpression} 'agent' '(' target=Expression ')' '{' options+=AgentOption* '}' ;
AgentTask: 'task' value=TaskString ;
AgentInput: 'input' payload=ObjectLiteral ;
AgentTools: 'tools' (none?='none' | '[' names+=ID (',' names+=ID)* ','? ']') ;
AgentWrite: 'write' paths=Expression ;
AgentExpect: 'expect' type=TypeRef ;
AgentTimeout: 'timeout' value=DURATION ;
AgentRetry: 'retry' '{' 'attempts' ':' attempts=NUMBER (',' 'on' ':' '[' codes+=RetryErrorCode (',' codes+=RetryErrorCode)* ','? ']')? ','? '}' ;

ParallelExpression infers Expression: {infer ParallelExpression} 'parallel' '{' branches+=ParallelBranch (',' branches+=ParallelBranch)* ','? '}' ;
ParallelBranch: name=Id '=' value=Expression ;
ParallelMapExpression infers Expression: {infer ParallelMapExpression}
'parallel' 'map' source=Expression 'as' item=Id ('limit' limit=NUMBER)? '{' body=Expression '}' ;
VerifyExpression infers Expression: {infer VerifyExpression} 'verify' '{' checks+=VerifyCheck+ 'pass' 'when' condition=Expression '}' ;
CheckItem: 'check' name=Id '=' value=Expression ;
ParallelCheckGroup: 'parallel' '{' checks+=CheckItem+ '}' ;

WorkflowMember: UseStatement | LimitsBlock | TypeDeclaration | Pipeline ;
PipelineStatement: Stage | LetStatement | RequireStatement | EmitStatement | ReturnStatement | IfStatement ;
StageStatement: LetStatement | RequireStatement | EmitStatement | ReturnStatement | IfStatement ;
AgentOption: AgentTask | AgentInput | AgentTools | AgentWrite | AgentExpect | AgentTimeout | AgentRetry ;
VerifyCheck: CheckItem | ParallelCheckGroup ;

Appendix: diagnostic catalog

Compile time

CodeSeverityMeaning
syntax / lexerrorparse / lex errors
duplicate-workflowerrorduplicate workflow name
duplicate-aliaserrorduplicate use alias
duplicate-limits / duplicate-pipelineerrormultiple limits / pipeline
duplicate-type / duplicate-type-fielderrorduplicate type / field
recursive-typeerrorrecursive type reference
unknown-teamerrormember<T> referencing a non-team alias
invalid-boundserrorinvalid array bounds
external-type / unknown-typeinfo / errorboundary external; internal unknown rejected
duplicate-stageerrorduplicate stage name
unknown-stage / duplicate-dependencyerrormissing or duplicated dependency
forward-dependencyerrordepending on a later stage
undeclared-dependencyerrorreading an undeclared stage result
missing-pipeline / missing-inputerrormissing pipeline / input param
workflow-param-nameerrorparameter not named input
missing-return / pipeline-return-positionerrorpipeline return missing / misplaced
stage-missing-return / stage-return-positionerrorstage return missing / misplaced
stage-return-mismatch / workflow-return-mismatcherrorreturn type mismatch
duplicate-variableerrorsame-scope variable reuse
unknown-variableerrorundefined variable (incl. shorthand fields)
heterogeneous-arrayerrorincompatible array literal element types
require-not-bool / if-not-bool / verify-predicate-not-boolerrornon-bool condition
dangerous-member-accesserroraccessing a dangerous member
invalid-member-accesserrormember absent on the type
optional-member-accesserrormember access attempted before handling absence with ??
invalid-projectionerror[*] on a non-array, or bare projection
unknown-builtinerrorcalling a non-builtin
invalid-builtin-callerrorbuiltin argument shape mismatch
invalid-agent-targeterrorillegal agent() target form
invalid-member-argumenterrorteam.member() argument not a member id
agent-missing-task / agent-missing-expecterrormissing required option
duplicate-agent-option / duplicate-toolerrorduplicated option / tool name
expect-invalid-typeerrorexpect type not Schema-generatable
write-not-path-arrayerrorwrite not path[]
invalid-timeout / invalid-retry / invalid-limit / invalid-limitserrorinvalid option value
invalid-emiterroremit missing its payload object
duplicate-branch / duplicate-check / duplicate-object-fielderrorduplicate branch / check / field
operator-typeerroroperator operand type mismatch
parallel-map-source-not-arrayerrorparallel map source not an array

Runtime

See Runtime, sandbox & errors.