语言参考
Agent Flow 语言的完整参考。权威实现:
packages/lang/src/language/agent-flow.langium(文法)与
packages/compiler/src/checker.ts(静态语义)。
词法
注释与空白
- 行注释
// ...;块注释/* ... */(可跨行,不可嵌套) - 空白与注释是隐藏 token;语言不区分换行
标识符
ID = /[_a-zA-Z][\w$]*/
字符串
| 形式 | 说明 |
|---|---|
"..." | 单行;转义 \" \\ \n \t \r,其余 \x 视为 x |
"""...""" | 多行;内容逐字保留;用于 task,仅裁掉首尾换行 |
数值与时长
| Token | 正则 | 说明 |
|---|---|---|
NUMBER | /\d+(\.\d+)?/ | 十进制;无负数字面量 |
DURATION | /\d+(ms|s|m|h)(?![a-zA-Z0-9_])/ | 500ms 5s 10m 2h;统一换算毫秒 |
分隔符惯例
| 结构 | 逗号 |
|---|---|
| 数组、tools、stage 依赖、调用实参、retry 错误码 | 必需;适用处允许尾逗号 |
| 对象、type、limits、parallel 分支 | 必需;允许尾逗号 |
| workflow/stage/agent/verify 语句 | 不使用逗号 |
类型
TypeRef := PrimaryTypeRef Suffix*
PrimaryTypeRef := 'member' '<' Id '>' | Id
Suffix := '[' RangeBounds? ']'
RangeBounds := NUMBER '..' NUMBER
重复后缀可表达嵌套数组(path[][]);可选字段写作 field?: Type。
读取可选字段得到内部 T?,用 value ?? fallback 消除缺失可能;null 是显式空值。
类型种类、解析顺序、可赋值性与 JSON Schema 映射见 类型系统一页的完整表格。
顶层声明
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 (',' ...)* ','?)? '}'
管道与阶段
Pipeline := 'pipeline' ID? '{' PipelineStatement* '}'
Stage := 'stage' ID ('after' ID (',' ID)*)? '->' TypeRef '{' StageStatement* '}'
- pipeline 的顶层最后一个语句必须是
return;if内允许类型安全的提前返回 - stage 名即结果变量;依赖必须已声明、源序在前; 体内只能读取已声明依赖的结果
- V1 按源序执行;依赖进入 IR 供 DAG 调度
语句
| 语句 | 文法 | 约束 |
|---|---|---|
| let | let ID = 表达式 | 同作用域不可重名 |
| require | require 表达式 else fail STRING | 条件必须 bool |
| emit | emit progress 对象字面量 | 仅 progress 事件 |
| return | return 表达式 | 类型须匹配声明 |
| if | if 表达式 { 语句+ } (else { 语句+ })? | 条件必须 bool;块作用域 |
表达式
优先级(由紧到松)
后缀 .member [*]投影 f(args)
比较 == != < <= > >= / in a..b(不可链式)
not not (作用于整个比较级操作数)
and and (左结合)
or or (左结合)
空值合并 ?? (右结合,最松)
not a == b 解析为 not (a == b);圆括号可显式分组。
optional ?? fallback 返回非可选的兼容公共类型。
字面量与基本形式
- 字符串/多行字符串/数值/布尔/时长
- 数组字面量
[e1, e2](逗号必需;推断兼容的公共元素类型) - 对象字面量
{ f1, f2: e2 }:name: expr显式字段;name简写字段 等价name: name,name 必须是可见变量 - 成员访问
expr.member:- 对象类型 → 字段类型(不存在报
invalid-member-access) - 数组 → 仅
.count(number)、.empty/.any(bool) Verification→.passed/.failed/.checks- 外部类型 / context(any)→ 任意成员,结果 any
- 团队绑定 →
.main(agent-target) - 危险成员名
constructorprototype__proto____defineGetter____defineSetter____lookupGetter____lookupSetter__hasOwnPropertyisPrototypeOfpropertyIsEnumerabletoLocaleStringtoStringvalueOf一律拒绝(dangerous-member-access)
- 对象类型 → 字段类型(不存在报
- 投影
xs[*].field:xs必须数组;等价xs.map(x => x.field); 结果为字段类型数组;裸投影xs[*]不合法 - 调用
f(args):仅内建函数与team.member(...)(后者只能在 agent() 的 target 位置)
内建函数
| 函数 | 参数 | 结果 | 语义 |
|---|---|---|---|
unique(array) | 数组 | bool | 元素(按字符串化)无重复 |
disjoint(arrayOfArrays) | 数组的数组 | bool | 子数组两两无公共元素 |
union(arrayOfArrays) | 数组的数组 | 数组 | 顺序拼接所有子数组 |
range 与 in
a .. b:两侧须数值/时长;语法上只能作为in的右操作数x in a..b:闭区间,x >= a && x <= b- 比较、
in和范围不可链式书写
agent() 调用
AgentExpression := 'agent' '(' Expression ')' '{' AgentOption* '}'
AgentOption :=
'task' TaskString # 必需,唯一
| 'input' ObjectLiteral
| 'tools' 'none' | '[' (','? Id)* ']'
| 'write' Expression # 类型须为 path[]
| 'expect' TypeRef # 必需,唯一;不可 Verification/外部类型
| 'timeout' DURATION # 正时长
| 'retry' '{' 'attempts' ':' NUMBER ('on' ':' '[' Code (',' Code)* ']')? '}'
Code := 'timeout' | 'rate_limit' | 'transient'
target 三形态:别名 / team.main / team.member(expr)。
attempts 为额外次数;on 缺省 ['transient'];判定按错误码。
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:分支并发,barrier 语义;结果
{分支名: 类型} - parallel map:源须数组;
limit正整数;结果顺序与输入一致;类型T[] → U[] - verify:check 名唯一;check 初始化在外层作用域,
pass when在 verify 作用域;结果Verification<{checks...}>
作用域
workflow :input、context(声明类型时强类型,否则 any)、use 别名
pipeline :+ 已完成 stage 结果(源序渐进)
stage :+ 显式声明的依赖
block :if then/else
map :循环变量
verify :check 名(仅 pass when 可见)
保留字与字段软关键字
以下词在变量、stage、type、资源别名、parallel/check/map 名称等位置是保留字:
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
在 type 字段、对象字段和 .field 成员访问位置,它们是软关键字,
type Event { type: text, retry?: bool } 合法。input、context、
duration、member 也可作普通值标识符。该区分让外部 JSON 字段名
保持自由,同时保护会进入代码生成命名空间的声明名。
JS 风格的 while、for、function、class 不是保留字——但 V1
语法不存在这些语句,写出来同样报语法错误。
高频撞名场景
| 场景 | ✗ | ✓ |
|---|---|---|
| map 循环变量 | as task / as check / as map | item / job / slot |
| check 名 | check task = ... | tests / security / policy |
| parallel 分支名 | tools = ... | frontend = ...(普通名词) |
| 对象显式字段 | { type: value } | 合法 |
| let 变量/对象简写 | let expect = ... / { expect } | 避开关键字 |
附录:EBNF 文法
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 ;
附录:诊断码目录
编译期
| 码 | 级别 | 含义 |
|---|---|---|
syntax / lex | error | 解析/词法错误 |
duplicate-workflow | error | workflow 重名 |
duplicate-alias | error | use 别名重复 |
duplicate-limits / duplicate-pipeline | error | 多个 limits / pipeline |
duplicate-type / duplicate-type-field | error | type / 字段重名 |
recursive-type | error | 类型递归引用 |
unknown-team | error | member<T> 引用非 team 别名 |
invalid-bounds | error | 数组界非法 |
external-type / unknown-type | info / error | 边界 external;内部未声明类型拒绝 |
duplicate-stage | error | stage 重名 |
unknown-stage / duplicate-dependency | error | 依赖不存在或重复 |
forward-dependency | error | 依赖源序更晚的 stage |
undeclared-dependency | error | stage 读取未声明依赖的结果 |
missing-pipeline / missing-input | error | 缺少 pipeline / input 形参 |
workflow-param-name | error | 形参未命名为 input |
missing-return / pipeline-return-position | error | pipeline return 缺失/位置错误 |
stage-missing-return / stage-return-position | error | stage return 缺失/位置错误 |
stage-return-mismatch / workflow-return-mismatch | error | 返回类型不匹配 |
duplicate-variable | error | 同作用域变量重名 |
unknown-variable | error | 未定义变量(含简写字段) |
heterogeneous-array | error | 数组元素类型不兼容 |
require-not-bool / if-not-bool / verify-predicate-not-bool | error | 条件非 bool |
dangerous-member-access | error | 访问危险成员 |
invalid-member-access | error | 类型上不存在该成员 |
optional-member-access | error | 未先用 ?? 处理缺失值便继续访问其成员 |
invalid-projection | error | [*] 作用于非数组,或裸投影 |
unknown-builtin | error | 调用非内建函数 |
invalid-builtin-call | error | 内建函数参数形态不符 |
invalid-agent-target | error | agent() 目标形态非法 |
invalid-member-argument | error | team.member() 参数非成员 id |
agent-missing-task / agent-missing-expect | error | 缺少必需选项 |
duplicate-agent-option / duplicate-tool | error | agent 选项/工具名重复 |
expect-invalid-type | error | expect 类型不可生成 Schema |
write-not-path-array | error | write 非 path[] |
invalid-timeout / invalid-retry / invalid-limit / invalid-limits | error | 选项值非法 |
invalid-emit | error | emit 缺少载荷对象 |
duplicate-branch / duplicate-check / duplicate-object-field | error | 重名分支/检查/字段 |
operator-type | error | 运算符操作数类型不符 |
parallel-map-source-not-array | error | parallel map 源非数组 |