关于 write-flow
write-flow 是面向 AI Agent 的技能指南,说明如何用 Windmill CLI 创建和测试 Windmill 工作流。指南强调正确生成骨架、遵循结构规则,并按用户意图选择本地预览或部署操作。
主要功能
- 01使用 `wmill flow new` 创建工作流骨架
- 02按 OpenFlow 规则定义模块
- 03选择整条或单步预览
- 04重新生成本地元数据和锁文件
- 05区分测试与部署
适用场景
- 01按指定路径和摘要创建 Windmill 工作流
- 02测试本地修改而不部署
- 03迭代时验证单个模块
- 04更新内联脚本的元数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ windmill-labs/ windmill-cli-docs --skill write-flow安装后在 Claude Code 中输入/write-flow确认已加载。
详细介绍
Windmill Flow CLI 指南
创建 Flow
你——AI Agent——应通过使用正确的标志运行 wmill flow new <path>,自行搭建 Flow。不要手动创建文件夹和 flow.yaml,也不要告诉用户“运行 wmill flow new 并按照提示操作”。
wmill flow new 会使用正确的后缀(根据工作区的 nonDottedPaths 设置,使用 __flow 或 .flow),创建文件夹、写入最简的 flow.yaml 框架,并打印针对 Claude 的后续步骤提示。手动搭建会跳过这些操作,而且经常会选错后缀。
第 1 步——询问用户路径和摘要
你需要两项信息:
- 路径——windmill 路径,例如
f/folder/my_flow或u/username/my_flow。 - 摘要——对 Flow 的简短描述。
如果用户的请求没有提供这两项信息,请在同一轮中一并询问。使用运行环境提供的交互式提问功能——如果有结构化多选工具,就使用该工具;否则通过普通聊天询问——并为每项提供一两个示例值(同时提供“其他”/自由填写选项)。不要猜测路径或摘要。
第 2 步——自行运行命令
wmill flow new f/folder/my_flow --summary "Short description"
如果用户提供了值得与摘要分开保留的详细说明,请添加 --description "..."。
第 3 步——填写 flow.yaml
打开生成的 flow.yaml(位于刚才创建的文件夹中),将空的 value.modules 和 schema 替换为实际的 Flow 定义。
对于 rawscript 模块,请使用 !inline path/to/script.ts 作为内容键。内联脚本文件名不应包含 .inline_script.(例如,使用 a.ts,而不是 a.inline_script.ts)。
Flow 填入实际内容后,主动询问是否要打开可视化预览,并用一句话提出建议(例如:“要我打开可视化预览吗?”)。不要自动打开——打开开发页面会产生副作用(打开浏览器窗口,还可能添加 launch.json 条目),应先征得用户同意。
应避免的反模式
- ❌ 不运行
wmill flow new,而是手动创建__flow文件夹和flow.yaml。这会跳过后缀设置解析、默认结构和 Claude 提示。 - ❌ 告诉用户“运行
wmill flow new <path>”——你可以而且应该自行运行。 - ❌ 不询问用户,而是自行编造路径或摘要。
CLI 命令 — 运行、预览、部署
编写完成后,应根据用户的意图采取行动,而不是只列出命令。适合时,请自行运行 wmill flow preview(参见下文“编写完成后 — 主动提出运行,不要被动等待”)。wmill generate-metadata 会重新生成本地锁定/哈希文件(不是部署),但会重新解析依赖项——应先提出此操作,并在用户同意后运行;除非项目的 AGENTS.md 指明可以自动运行元数据生成。只能提及 wmill sync push(部署),让用户批准后再执行。可选操作:
wmill flow preview <flow_path>— 在本地流程上迭代时的默认选项。 使用本地内联脚本运行本地flow.yaml,而不会进行部署。添加--remote可让 PathScript 步骤使用已部署到工作区的脚本,而不是本地文件。添加--step <step_id>可单独运行一个模块(参见下文“单步与全流程预览”)。wmill flow run <path>— 运行已部署到工作区的流程。仅当用户明确希望测试已部署的版本,而不是本地修改时使用。wmill generate-metadata— 为流程及其内联脚本重新生成过时的本地.lock文件,并刷新wmill-lock.yaml中的内容哈希。只会写入本地文件(不会部署)。编辑了导入项或参数发生变化的内联脚本后,请运行此命令,以免wmill-lock.yaml与实际状态不一致,并在 git-sync/CI 中引入噪声。默认情况下,它会扫描整个工作区的脚本、流程和应用,但只重新生成过时的文件;传入流程所在的文件夹作为参数(或从该子目录运行),即可将范围限制为你编辑的流程。请注意,导入了已更改共享脚本的流程(或脚本)也会被纳入;应用前运行wmill generate-metadata --dry-run,即可准确查看哪些内容已过时以及原因(content changed与depends on <path>)。- 通过
git push或wmill sync push将本地更改部署到工作区,具体取决于仓库的配置方式(参见AGENTS.wmill.md中的 部署 部分)。只有用户明确要求部署/发布/推送时,才建议或执行部署;用户说“运行”“试试”或“测试”时,不要部署。
预览与运行 — 根据意图选择,不要习惯性操作
如果用户说“运行流程”“试一下”“测试一下”或“能正常工作吗”,且存在对 flow.yaml 的本地修改,请使用 flow preview。不要先推送流程再用 flow run——推送属于部署,仅为了测试而部署会用未经测试的修改覆盖工作区版本。
仅在以下情况下使用 flow run:
- 用户明确说“运行已部署的版本”/“运行服务器上的版本”。
- 没有正在编辑的本地
flow.yaml(你只是在调用已有流程)。
仅在以下情况下使用 sync push:
- 用户明确要求部署、发布、推送或交付。
- 预览已验证更改,且用户希望将其放入工作区。
单步与全流程预览
当用户只在迭代一个模块,且流程的上游步骤并非其验证目标时,使用 flow preview <flow_path> --step <step_id>。此命令只会运行该步骤对应的可运行项(rawscript:内联脚本;script:PathScript,如有本地文件则使用本地文件;flow:按路径运行子流程),比运行整个流程快得多,尤其是在前序步骤运行缓慢或成本高昂时。步骤 ID 通过遍历嵌套的 branchone/branchall/forloopflow/whileloopflow 模块来解析,并包含特殊的 preprocessor 和 failure 模块。
当各步骤依赖彼此的输出、用户正在验证整体控制流,或 --step 不适用时(branchone、branchall、forloopflow、whileloopflow、identity 和 AI agent 步骤都不能单独测试;对于 branchone/branchall/forloopflow/whileloopflow,可传入内部步骤的 ID 来测试其中的步骤),请使用 flow preview <flow_path>(不带 --step)。
编写完成后——主动提出运行,不要被动等待
这里指的是程序化执行(wmill flow preview -d '<args>'),它会实际运行流程并产生副作用。视觉预览(preview 技能)会单独提供——请参阅下文的“视觉预览”。
如果用户还没有告诉你运行或测试流程,就用一句话将其作为下一步提出(例如:“要我用示例参数运行 wmill flow preview 吗?”)。不要提供多选菜单。
如果用户已在原始请求中要求测试、运行或试用流程,就跳过询问,直接执行 wmill flow preview <path> -d '<args>'——根据流程的输入架构选择合理的参数。
架构中类型为资源的输入(format: resource-<type>)应将字符串 "$res:<path>" 作为其完整值——-d '{"db": "$res:f/databases/postgres_prod"}',而不是 {"db": {"$res": "..."}},也不是普通路径。变量也一样,使用 "$var:<path>"。请参阅 resources 技能。
你可以安全地自行运行 wmill flow preview(它不会部署)。wmill generate-metadata 也不会部署(它只会写入本地锁定/哈希文件),但会重新解析依赖——提出运行它,并在用户同意后运行,除非项目的 AGENTS.md 允许自动生成元数据。运行后,检查重新生成的 .lock 差异,并告知用户哪些内联脚本依赖版本发生了变化,以便他们在部署前发现不想要的版本升级。只有 wmill sync push 会部署;仅在用户明确要求时运行。
视觉预览
要在开发页面中以可视化方式打开流程(图表 + 实时重新加载),请使用 preview 技能。始终用一句话将其作为下一步提出(例如:“要我打开视觉预览吗?”),而不要自动打开——打开开发页面会产生副作用(浏览器窗口,且在 MCP-preview 分支下可能会添加 launch.json 条目),用户应当对此表示同意。如果用户已在原始请求中要求查看、预览或可视化流程,就跳过询问,直接调用该技能。
Windmill 流程构建指南
OpenFlow 架构
OpenFlow 架构(openflow.openapi.yaml)是流程结构的事实依据。有关完整的类型定义,请参阅 OPENFLOW_SCHEMA。
保留的模块 ID
failure- 为失败处理模块保留preprocessor- 为预处理器模块保留Input- 为流程输入引用保留
严格结构规则
这些是 Windmill 架构中的严格规则。请严格遵守。
value.modules仅用于常规顺序步骤value.preprocessor_module和value.failure_module是value内的特殊顶层字段,不是value.modules中的条目- 如果流程需要预处理器,请创建
value.preprocessor_module,并设置id: preprocessor - 如果流程需要失败处理器,请创建
value.failure_module,并设置id: failure - 不要在
value.modules中创建名为preprocessor或failure的常规模块 preprocessor_module和failure_module仅支持script或rawscriptpreprocessor_module在常规模块之前运行,且不能引用results.*failure_module可以使用error对象中的error.message、error.step_id、error.name和error.stack- 即使流程运行了
failure_module,最终状态仍为失败;除非处理器返回带有recover: true的对象,此时流程最终状态为成功。即使失败步骤位于循环、分支或子流程中,也适用此规则。recover永远不会改变哪些步骤会运行;循环或分支在失败后是否继续,仍由其自身设置(skip_failures、continue_on_error)决定
正确结构:
value:
preprocessor_module:
id: preprocessor
value:
type: rawscript
...
failure_module:
id: failure
value:
type: rawscript
...
modules:
- id: process_event
value:
type: rawscript
...
错误结构:
value:
modules:
- id: preprocessor
...
- id: process_event
...
- id: failure
...
模块 ID 规则
- 在整个流程中必须唯一
- 使用下划线,不要使用空格(例如,使用
fetch_data,而不是fetch data) - 使用能体现步骤用途的描述性名称
AI Agent 模块
aiagent 模块运行一个可以调用工具的大模型。value.tools 中的每个条目都是一个形似模块的对象,并带有额外的 value.tool_type:脚本/流程工具使用 flowmodule,MCP 服务工具使用 mcp,网页搜索使用 websearch,内置的 ask_question 工具使用 question(Agent 会停止运行,不占用工作进程,直到有人在运行页面或聊天中作答;value.timeout 可选,单位为秒,默认值为 1800)。在 flowmodule 或 mcp 工具上设置 requires_approval: true,即可在每次调用运行前由人员批准或拒绝。
{
"id": "support_agent",
"summary": "AI agent for customer support",
"value": {
"type": "aiagent",
"input_transforms": {
"provider": {
"type": "static",
"value": { "kind": "openai", "resource": "$res:f/ai_providers/openai", "model": "gpt-4o" }
},
"output_type": { "type": "static", "value": "text" },
"user_message": { "type": "javascript", "expr": "flow_input.query" },
"system_prompt": { "type": "static", "value": "You are a helpful assistant." }
},
"tools": [
{
"id": "search_docs",
"summary": "search_documentation",
"description": "Search the product documentation. Use it whenever the user asks how a feature works.",
"value": {
"tool_type": "flowmodule",
"type": "rawscript",
"language": "bun",
"content": "export async function main(query: string) { return ['doc1', 'doc2']; }",
"input_transforms": { "query": { "type": "static", "value": "" } }
}
}
]
}
}
provider是一个对象,而不是一个单独的资源字符串:{ "kind": <provider kind>, "resource": "$res:<path>", "model": <model id> }。除非模块通过value.agent链接到已保存的 Agent,否则此项为必填项。从表单运行流程时,使用静态配置即可;聊天流程则会将其字段连接到流程输入——详见下文
聊天模式流程
设置了 value.chat_input_enabled: true 的流程会通过聊天而不是表单运行:消息输入框每轮发送一条消息,并呈现整个对话。流程需要一个必填的 user_message 字符串输入,供 Agent 读取。消息输入框本身不会编辑的其他流程输入,会在“配置输入”中询问。
静态的 provider 会导致聊天无法更改模型。 可以通过流程输入为其赋值,方式有两种:一个承载整个对象的输入("expr": "flow_input.model_config")会让每个字段都可编辑;也可以逐个连接字段,固定部分字段并显示其他字段。聊天可以写入的字段会成为消息输入框中的控件——提供商选择器、模型列表、思考控制项等;静态字段则保持固定,不会显示控件。kind 是唯一例外:只有与 resource 一起设置时,消息输入框才会写入 kind,因为提供商是成对选择的。因此,单独连接的 kind 输入仍会在“配置输入”中作为待填写项,而运行所需的内容不会因此变得无法访问。
{
"id": "chat_agent",
"value": {
"type": "aiagent",
"input_transforms": {
"provider": {
"type": "javascript",
"expr": "({ kind: 'anthropic', resource: '$res:f/ai/claude', model: flow_input.model, reasoning_effort: flow_input.thinking })"
},
"user_message": { "type": "javascript", "expr": "flow_input.user_message" },
"user_attachments": { "type": "javascript", "expr": "flow_input.files" },
"memory": { "type": "static", "value": { "kind": "compaction" } },
"streaming": { "type": "static", "value": true },
"output_type": { "type": "static", "value": "text" }
},
"tools": []
}
}
- 逐个连接字段时,必须使用一个对象字面量,其值为字面量或不带其他内容的
flow_input.x引用。扩展运算符、函数调用或计算属性键都会导致消息输入框无法判断哪个输入对应哪个字段,因此不会提供任何控件——如果整个对象直接使用flow_input.x,则会将其视为一个包含所有字段的输入 - 链接到已保存 Agent 的步骤(已设置
agent)自身没有provider。它通过model和reasoning_effort输入提供相同的两个控件,用来替换 Agent 中保存的设置:"model": { "type": "javascript", "expr": "flow_input.model" }。模型为空时会保留已保存的模型,effort 为空时不会发送任何值,提供商资源则始终使用 Agent 的资源 memory使 Agent 能够看到先前的轮次;没有它,每条消息都会从头开始- 启用
streaming后,答案和思考过程会逐个 token 显示,而不是一次性显示 user_attachments指向一个类型为 S3 对象数组的流程输入 ({ "type": "array", "items": { "type": "object", "resourceType": "s3object" } }),这样随消息发送的文件就能传给 Agent- 运行时需要一个
memory_id查询参数,而不是流程参数,用于指定该轮所属的对话:新的 UUID 会启动一个对话,重复使用 UUID 则会继续该对话。聊天会自动提供此参数;通过其他方式运行时,必须传入此参数,否则服务器会拒绝该任务
工具命名规则
这些规则适用于 flowmodule 工具,也就是 Agent 按名称调用的工具。websearch 或 question
工具的 summary 是普通标签(Web Search),而 mcp 工具会公开 MCP
服务自身的工具名称,因此完全不会检查名称——这些工具的 summary 保持不变。
summary是 Agent 调用工具时使用的名称,不是面向用户的标签。将易于理解的说明写在description中summary必须匹配^[a-zA-Z0-9_]+$:只能包含字母、数字和下划线。不能有空格、连字符、 点号或重音符号——应使用search_documentation,而非Search documentation- 始终设置
summary。它在该 Agent 的工具中必须唯一,且不能是保留 ID(do、bg、ctx、state、if、else、for、delete、while、new、in、failure、preprocessor、as、Input、Result、Trigger)之一 - 工具名称包含上述字符集之外的字符时,任何将该工具提供给 Agent 的运行都会失败,并显示
Invalid tool name。wmill lint <flow folder>会在任何内容运行前报告此问题 - 工具
id遵循与任何模块 ID 相同的规则——在整个流程中唯一,使用下划线而非空格 description是可选的自由文本,用于告诉 Agent 何时以及如何调用工具。如果仅凭名称无法清楚说明, 就应设置它;它会覆盖根据底层脚本生成的说明
AI 决策模块
aidecision 模块会向决策模型(TypeSafe 的 Jev、Cloudflare 的 Clef 或 OpenAI 的 Decisions API)提出关于某个 state 的类型化问题,
并为每个问题返回经过校准的概率,而非文本。如果某个步骤属于判断(分类、路由、评分或是/否检查),无需工具也无需自由文本,
优先使用它而非 aiagent:它速度更快、成本更低,且返回结果的结构固定。
{
"id": "triage",
"summary": "Classify the ticket",
"value": {
"type": "aidecision",
"input_transforms": {
"provider": {
"type": "static",
"value": { "kind": "typesafe", "resource": "$res:f/ai/typesafe", "model": "jev-latest" }
},
"state": { "type": "javascript", "expr": "flow_input.message" },
"questions": {
"type": "static",
"value": {
"intent": {
"type": "choice",
"instructions": "What does the customer want?",
"criteria": { "refund": "Money back", "bug": "Something is broken", "other": "Anything else" }
},
"urgency": {
"type": "score",
"instructions": "How urgent is the ticket?",
"criteria": ["Can wait", "This week", "Today", "Right now"]
},
"angry": { "type": "noul", "instructions": "Is the customer angry?" }
}
}
}
}
}
provider.kind为typesafe(除非固定某个版本,否则model为jev-latest)、cloudflare(model为clef,或使用clef-flash获得更快的回答)或openai(model为gpt-6-luna,不能使用 chat model);资源必须与其类型相同state是要评估的内容:通常是文本,例如要分类的消息。若要组合多个值,请传入一个对象,其中包含说明性键,且仅包含问题所需的内容 (({ message: flow_input.message, plan: results.get_account.plan }));字符串数组也可用questions将每个问题名称映射到{ type, instructions, criteria }:choice:criteria将每个选项映射到其说明(最多 255 个选项)。答案包含choice、probabilities和confidencescore:criteria是一个包含 2 到 10 个等级说明的有序数组。答案包含score、legend、probabilities和confidencenoul:是/否,criteria可选地包含{ "true": ..., "false": ... }说明。答案包含noul,即是的概率,范围为 0 到 1
- 结果为
{ output, model, usage },其中output按问题名称作为键,因此后续步骤可以读取results.triage.output.intent.choice、results.triage.output.urgency.score或results.triage.output.angry.noul > 0.7 - 在
openai中,同一组questions会转换为其 Decisions API 所需的格式,答案读取方式也相同(choice、score、noul),但probabilities是{ value, probability }的列表(score还包含label),且评分结果不含legend。如果 OpenAI 拒绝回答某个问题,该步骤就会失败。state如果是用户消息数组([{ role: "user", content: [{ type: "input_text", text }, { type: "input_image", image_url: "data:image/png;base64,..." }] }]), 会按原样发送,这也是评估图像的方式;其他对象或数组则会作为 JSON 文本发送
根据答案分支
要根据答案进行路由,请在决策步骤后立即放置一个 branchone,每个选项对应一个分支,并让每个
expr 都按 ID 读取决策结果:
{
"id": "route",
"value": {
"type": "branchone",
"branches": [
{ "summary": "refund", "expr": "results.triage.output.intent.choice === 'refund'", "modules": [...] },
{ "summary": "bug", "expr": "results.triage.output.intent.choice === 'bug'", "modules": [...] }
],
"default": [...]
}
}
对于是/否问题,设置一个分支,其表达式为 "expr": "results.triage.output.angry.noul >= 0.5"(根据需要调整
阈值),并将 default 设为“否”路径。
作为 Agent 工具
aidecision 可以作为 aiagent 的 flowmodule 工具("tool_type": "flowmodule", "type": "aidecision"):将 state 设为 { "type": "ai" },由调用模型提供该值,并且只有 output
会返回给模型。其 summary 需遵循工具命名规则。只有流程自身的 Agent 步骤可以调用它,不能通过用作工具的 Agent 调用。
常见错误
- 缺少
input_transforms:没有它们,Rawscript 参数将无法接收值 - 引用后续步骤:
results.step_id仅适用于当前步骤之前执行的步骤 - 模块 ID 重复:流程中的每个模块 ID 都必须唯一
- AI Agent 的 flowmodule 工具名称中包含空格:
summary就是工具名称,且只允许使用字母、数字和下划线
步骤间的数据流
flow_input.property- 访问流程输入参数results.step_id- 仅当某个先前步骤的结果处于作用域内时,访问该步骤的输出results.step_id.property- 仅当某个先前步骤的结果处于作用域内时,访问该步骤输出中的特定属性flow_input.iter.value- 在forloopflow中访问当前迭代值;在whileloopflow中,它只是迭代索引(普通数字,与flow_input.iter.index相同)flow_input.iter.index- 在循环内(forloopflow或whileloopflow)访问当前循环索引- 在挂起步骤(审批)之后,下一步骤的 JavaScript 表达式还会获得:
resume- 上一次恢复时的载荷(恢复表单值);使用continue_on_disapprove_timeout时,拒绝或超时将改为返回{ error: { name: "SuspendedDisapproved" | "SuspendedTimedOut", message } }resumes- 按到达顺序排列的所有恢复载荷approvers- 一个普通字符串数组,每次恢复对应一个字符串:如果通过getResumeUrls(approver)生成的resume/cancelURL 恢复,则为传入该函数的approver(如果有);否则(包括通过approvalPage链接恢复),为恢复用户的用户名;如果没有用户名,则为"anonymous",例如approvers[0]。它不是流程状态中存储的{ resume_id, approver }对象,因此approvers[0].approver始终为 undefined
循环结构规则
forloopflow会针对iterator的每个元素运行一次其modules;iterator是一个返回数组的 JavaScript 表达式(例如results.get_items);parallel: true会并发运行各次迭代,skip_failures: true会在某次迭代失败后继续运行- 对于
whileloopflow,使用模块级别的stop_after_if来中断循环:可以放在循环模块本身,也可以放在内部步骤上(当该步骤通过自身的results携带状态时必须放在步骤上,见下文) stop_after_if始终是流程模块上与id和value同级的字段,绝不能是循环value对象的直接键stop_after_all_iters_if用于在整个循环结束后进行检查,而非用于常规的逐次迭代中断条件stop_after_if会在每次迭代后求值:在循环模块上时,result是该次迭代的结果(即最后一个步骤返回的值);在内部步骤上时,result是该步骤的结果whileloopflow中的flow_input.iter.value只是迭代索引(与flow_input.iter.index的值相同),不会携带状态,因此flow_input.iter.value.<field>始终为 undefined,且依赖它的循环停止条件永远无法满足- 若要在迭代间传递状态,步骤应通过
results.<its_own_id>读取自身上一轮迭代的结果,并设置首轮迭代的回退值(例如results.b ?? flow_input.start)——但此时循环的stop_after_if必须放在该内部步骤上,而不能放在循环模块上:若循环体恰好只有一个普通步骤,且停止条件放在循环模块上,就会走快速路径,导致每次迭代中的results.<step_id>都为 null,循环永远无法终止(循环体包含 2 个或更多步骤,或其单个步骤自带stop_after_if、重试等配置时,无论停止条件放在哪里,results都会跨迭代解析) - 若要传递的状态只是计数器,应改为根据索引计算(例如
flow_input.iter.index + 1)——这种方式适用于所有配置,包括在循环模块上使用stop_after_if的情况 - 若用户需要循环结束后的标量或对象,应在循环后添加一个普通步骤,从循环结果中提取最终值,而不是返回整个循环结果数组
正确的 whileloopflow 结构:
- id: loop_until_done
stop_after_if:
expr: result.done === true
skip_if_stopped: false
value:
type: whileloopflow
skip_failures: false
modules:
- id: advance_state
value:
type: rawscript
input_transforms:
count:
type: javascript
expr: flow_input.iter.index + 1
- id: return_final_state
value:
type: rawscript
input_transforms:
final_state:
type: javascript
expr: results.loop_until_done[results.loop_until_done.length - 1]
通过 results 携带状态的正确 whileloopflow 结构(停止条件放在内部步骤上):
- id: loop_until_done
value:
type: whileloopflow
skip_failures: false
modules:
- id: advance_state
stop_after_if:
expr: result.done === true
skip_if_stopped: false
value:
type: rawscript
input_transforms:
state:
type: javascript
expr: results.advance_state ?? flow_input.initial_state
错误的 whileloopflow 模式:
- id: loop_until_done
value:
type: whileloopflow
stop_after_if:
expr: result.done === true
input_transforms:
state:
type: javascript
# iter.value is a number (the iteration index); there is no previous-iteration state
expr: flow_input.iter.value.count
input_transforms:
final_state:
type: javascript
expr: results.loop_until_done
审批 / 暂停结构
审批步骤是一个普通的 script 步骤(type: rawscript 或 type: script),添加模块级别的 suspend 后即可将其变为审批步骤。其脚本会调用 wmill.getResumeUrls(approver) 来生成机密的恢复 / 取消 URL,并返回这些 URL,以便发送给审批人(Slack、电子邮件等),或从运行页面进行审批。
suspend应放在流程模块对象上,与id和value同级- 绝不要将
suspend放在value内 - 不要对审批步骤使用
type: identity。identity 步骤会暂停,但不会生成恢复 URL,因此审批人没有可操作的链接——这不是有效的审批步骤。
正确结构:
- id: request_approval
suspend:
required_events: 1
resume_form:
schema:
type: object
properties:
comment:
type: string
required: [comment]
value:
type: rawscript
language: bun
input_transforms:
approver:
type: static
value: ''
content: |
import * as wmill from "windmill-client"
export async function main(approver?: string) {
const urls = await wmill.getResumeUrls(approver)
// send urls.resume / urls.cancel to the approver(s), e.g. via Slack or email
return urls
}
错误结构(suspend 错误地放在 value 内):
- id: request_approval
value:
type: rawscript
suspend:
required_events: 1
错误结构(identity 不会生成恢复 URL,因此不是真正的审批步骤):
- id: request_approval
suspend:
required_events: 1
value:
type: identity
分支结果作用域规则
branchone按顺序运行其branches中第一个expr为 true 的分支;如果没有,则运行其default模块;branchall则运行所有分支(parallel: true时并发运行)- 在分支内,可以引用之前的外层步骤和同一分支内之前的步骤
- 在
branchone外部,禁止引用仅存在于其分支或默认分支中的步骤 ID。请改用results.<branchone_module_id> - 在
branchall外部,禁止引用其分支中的步骤 ID。请改用results.<branchall_module_id> - 如果后续步骤在分支运行后需要稳定的数据结构,请确保每个分支返回相同的字段
- 必要时,在分支后立即添加一个标准化步骤,并在该步骤中使用
results.<branch_module_id>
branchone 后的正确示例:
- id: route_order
value:
type: branchone
...
- id: send_confirmation
value:
input_transforms:
routed:
type: javascript
expr: results.route_order
branchone 后的错误示例:
expr: results.create_shipment
expr: results.create_backorder
branchall 后的正确示例:
- id: enrich_parallel
value:
type: branchall
parallel: true
...
- id: combine_data
value:
input_transforms:
enrichments:
type: javascript
expr: results.enrich_parallel
输入转换
每个 rawscript 模块都需要 input_transforms,用于将函数参数映射到值:
静态转换(固定值): {"param_name": {"type": "static", "value": "fixed_string"}}
JavaScript 转换(动态表达式): {"param_name": {"type": "javascript", "expr": "results.previous_step.data"}}
资源引用
- 对于流程输入:使用类型
"object",格式为"resource-{type}"(例如,"resource-postgresql") - 对于步骤输入:使用静态值
"$res:path/to/resource"
重用现有脚本和流程
除非用户要求编写新代码,否则请先查找工作区中是否已有脚本或流程可完成某个步骤的任务,并按路径重用,而不是将其逻辑复制到 rawscript 中:
- 工作区脚本:
type: script,带有path(例如,f/folder/send_email) - 工作区流程,作为子流程运行:
type: flow,带有path - Hub 脚本:
type: script,路径为hub/<version>/<app>/<name>
该步骤的 input_transforms 必须涵盖所重用项目的输入,因此请先读取其输入架构。
在本地目录树和工作区中查找候选项(每个脚本旁边都有一个 .script.yaml 文件,其中包含其输入架构;每个流程文件夹中都有一个 flow.yaml),并使用 wmill script list / wmill flow list;wmill script get <path> 和 wmill flow get <path> 可显示项目详情。
给 AI 的补充提示词
流程的输入架构可以包含顶层 prompt_for_ai 字符串,脚本也可以包含:这是作者给 AI 的指示,用于指导 AI 选择输入。选择运行该流程的参数时,请遵循此指示;重写架构时也请保留该指示。
整理流程:分组与备注
分组和备注会影响流程在编辑器中的呈现方式,但都不会改变流程的行为。
每个非简单流程都应主动分段并分组,无需等待对方提出要求。 只要流程包含不止几个步骤,或连续步骤构成一个阶段(“获取”“转换”“通知”),就应将它们放入分组,并尽量让每个有意义的步骤都归入某个分组。谨慎使用备注,仅用于说明不适用于任何一段步骤的全流程信息:流程用途、关键假设、警告、TODO。通常一条备注就够了;不要用备注标注一连串步骤,这正是分组的用途。
value.groups 列出各个分组,每个分组覆盖从 start_id 到 end_id 的步骤:
start_id、end_id(必填):分组中首个和最后一个步骤的 ID;若两者 ID 相同,则表示单步骤分组summary:分组标题note:标题下方显示的 Markdown 文本color:可选值为yellow、blue、green、purple、pink、orange、red、cyan、lime、gray;不得使用十六进制代码或 CSS 颜色;省略时由编辑器选择autocollapse:设为true时,分组默认折叠
如果分组违反以下任何规则,编辑器就无法绘制流程:
start_id和end_id必须属于同一列表中的步骤:二者都在顶层,或都在同一个循环体或分支中。分组可以完整包含一个循环或分支步骤,但不能从其外部开始、在其内部结束- 在该列表中,
start_id不得位于end_id之后 - 分组可以嵌套(一个完全位于另一个内部),但不得部分重叠;也不得有两个分组同时使用相同的
start_id和end_id - 分组只能包含普通步骤:不得包含
preprocessor、failure、Input、Result、Trigger或 AI Agent 的工具
value.notes 列出便笺,每条便笺都包含唯一的 id、Markdown 格式的 text、取自同一列表的 color,以及 type: free。group 备注类型已弃用;请改用 value.groups。
为每条便笺指定 position({ x, y })和 size({ width, height }):如果未指定,编辑器会在原点绘制便笺,且无法调整其大小。设置 x: -400 和 width: 275 可将便笺放在图表旁边。
最终结构自检
定稿流程前,请确认:
- 任何预处理器都位于
value.preprocessor_module中 - 任何失败处理器都位于
value.failure_module中 - 任何审批步骤都有模块级
suspend - 下游步骤不会从分支外部引用分支内部步骤 ID
- 每个 AI Agent flowmodule 工具都有唯一的
summary,且仅由字母、数字和下划线组成 - 每个分组的起点和终点都属于同一列表中的步骤,起点在终点之前,分组可嵌套但不会部分重叠
wmill lint <flow folder>未报告任何错误
S3 对象操作
Windmill 内置支持与 S3 兼容的存储操作。
要将 S3 对象作为流程输入:
{
"type": "object",
"properties": {
"file": {
"type": "object",
"format": "resource-s3_object",
"description": "File to process"
}
}
}
在流程中使用资源
在 Windmill 中,凭据和配置存储在资源中。资源类型定义了资源的格式。
作为流程输入
在流程架构中,将属性类型设为 "object",格式设为 "resource-{type}":
{
"type": "object",
"properties": {
"database": {
"type": "object",
"format": "resource-postgresql",
"description": "Database connection"
}
}
}
找不到合适的资源类型时
连接仍应作为一个资源输入传入,而不是拆分为单独的字符串输入(例如 URL、令牌),也不是在步骤内读取变量:将输入类型设为 resource-c_<name>,并使用自定义资源类型来存储服务的连接字段;将密钥放在资源所引用的变量中。自定义类型的 c_ 前缀可将其与之后可能以相同名称从 hub 同步的类型区分开来。
- 如果实例显示尚未同步 Windmill Hub 的资源类型,请说明超级管理员可以执行同步,因为 hub 已为大多数服务提供资源类型。
- 否则,或用户倾向于不通过同步来解决时,请提出资源类型方案,并在创建前征求同意。
- 如果用户提出使用普通变量,也可以使用。
作为步骤输入(静态引用)
使用 $res: 前缀引用特定资源:
{
"database": {
"type": "static",
"value": "$res:f/folder/my_database"
}
}
OpenFlow 架构
{"OpenFlow":{"type":"object","description":"包含元数据、配置和流程结构的顶层流程定义","properties":{"summary":{"type":"string","description":"简要说明此流程的功能"},"description":{"type":"string","description":"此流程的详细文档"},"value":{"$ref":"#/components/schemas/FlowValue"},"schema":{"type":"object","description":"流程输入的 JSON Schema。用于定义输入参数、类型、默认值和验证规则。对于资源输入,将 type 设为 'object',并将 format 设为 'resource-'(例如 'resource-stripe')"},"on_behalf_of_email":{"type":"string","description":"此流程代表其运行的账户地址。读取时由 on_behalf_of 派生;写入时接受此字段,并将其解析为所指账户。"},"on_behalf_of":{"type":"string","description":"流程以此身份的权限运行:u/{username}、g/{group},或当用户名本身采用电子邮件地址格式时使用裸电子邮件地址。此身份中唯一存储的部分;on_behalf_of_email 由其派生。写入时可省略此字段,系统会根据该地址解析。"}},"required":["summary","value"]},"FlowValue":{"type":"object","description":"包含模块以及可选预处理器/失败处理器的流程结构","properties":{"modules":{"type":"array","description":"按顺序执行的步骤数组。每个步骤可以是脚本、子流程、循环或分支","items":{"$ref":"#/components/schemas/FlowModule"}},"failure_module":{"description":"流程失败时执行的特殊模块。接收包含 message、name、stack 和 step_id 的错误对象。其 id 必须为 'failure'。仅支持 script/rawscript 类型","$ref":"#/components/schemas/FlowModule"},"preprocessor_module":{"description":"外部触发时,在第一个步骤之前运行的特殊模块。其 id 必须为 'preprocessor'。仅支持 script/rawscript 类型。不能引用其他步骤的结果","$ref":"#/components/schemas/FlowModule"},"same_worker":{"type":"boolean","description":"如果为 true,所有步骤都在同一工作器上运行,以获得更好的性能"},"preserve_step_tags":{"type":"boolean","description":"如果为 true,且流程在自定义工作器标签上运行,则声明了自己的非空标签的步骤会在该标签上运行,而不是继承流程标签。未指定自身标签的步骤仍会继承流程标签。"},"concurrent_limit":{"type":"number","description":"此流程的最大并发执行数"},"concurrency_key":{"type":"string","description":"用于对并发执行进行分组的表达式(例如,按用户 ID 分组)"},"concurrency_time_window_s":{"type":"number","description":"concurrent_limit 的时间窗口(秒)"},"debounce_delay_s":{"type":"integer","description":"对流程执行进行防抖的延迟时间(秒)"},"debounce_key":{"type":"string","description":"用于对防抖执行进行分组的表达式"},"debounce_args_to_accumulate":{"type":"array","description":"在防抖执行期间累积的参数","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"作业可进行防抖的最长总时间(秒)"},"max_total_debounces_amount":{"type":"integer","description":"作业可进行防抖的最大次数"},"skip_expr":{"type":"string","description":"用于有条件地跳过整个流程的 JavaScript 表达式"},"cache_ttl":{"type":"number","description":"流程结果的缓存时长(秒)"},"cache_ignore_s3_path":{"type":"boolean"},"delete_after_secs":{"type":"integer","description":"如果设置,则在作业完成后经过此秒数,删除流程作业的参数、结果和日志"},"flow_env":{"type":"object","description":"所有步骤均可使用的环境变量。值可以是字符串、JSON 值或特殊引用:'$var:path'(工作区变量)或 '$res:path'(资源)。","additionalProperties":{}},"priority":{"type":"number","description":"执行优先级(数值越高,越先运行)"},"early_return":{"type":"string","description":"同步 Webhook 在此步骤完成后立即返回该步骤结果,其余流程继续运行。如果此步骤未运行,或在流程配置了失败模块时运行失败,则返回流程最终结果。"},"chat_input_enabled":{"type":"boolean","description":"此流程是否接受聊天式输入"},"notes":{"type":"array","description":"附加到流程的便笺","items":{"$ref":"#/components/schemas/FlowNote"}},"groups":{"type":"array","description":"用于组织模块的语义分组","items":{"$ref":"#/components/schemas/FlowGroup"}}},"required":["modules"]},"Retry":{"type":"object","description":"模块执行失败时的重试配置","properties":{"constant":{"type":"object","description":"以固定间隔重试","properties":{"attempts":{"type":"integer","description":"重试次数"},"seconds":{"type":"integer","description":"重试间隔秒数"}}},"exponential":{"type":"object","description":"使用指数退避重试:第 n 次重试的等待时间为 multiplier × seconds^n","properties":{"attempts":{"type":"integer","description":"重试次数"},"multiplier":{"type":"integer","description":"指数退避的乘数"},"seconds":{"type":"integer","minimum":1,"description":"指数的底数(秒);第 n 次重试的等待时间为 multiplier × seconds^n,其中 n 也包括固定间隔重试的次数"},"random_factor":{"type":"integer","minimum":0,"maximum":100,"description":"随机抖动百分比(0-100),用于避免惊群效应"}}},"retry_if":{"$ref":"#/components/schemas/RetryIf"}}},"FlowNote":{"type":"object","description":"附加到流程的便笺,用于记录文档和注释","properties":{"id":{"type":"string","description":"便笺的唯一标识符"},"text":{"type":"string","description":"便笺内容"},"position":{"type":"object","description":"便笺在流程编辑器中的位置","properties":{"x":{"type":"number","description":"X 坐标"},"y":{"type":"number","description":"Y 坐标"}},"required":["x","y"]},"size":{"type":"object","description":"便笺在流程编辑器中的大小","properties":{"width":{"type":"number","description":"宽度(像素)"},"height":{"type":"number","description":"高度(像素)"}},"required":["width","height"]},"color":{"type":"string","description":"便笺的颜色,可选值:yellow、blue、green、purple、pink、orange、red、cyan、lime、gray。其他值将不应用样式。"},"type":{"type":"string","enum":["free","group"],"description":"便笺类型——'free' 表示独立便笺。'group' 便笺已弃用;请改用 FlowValue.groups 对流程分段。"},"locked":{"type":"boolean","default":false,"description":"便笺是否已锁定,无法编辑或移动"},"contained_node_ids":{"type":"array","items":{"type":"string"},"description":"对于分组便笺,此分组中所含节点的 ID"}},"required":["id","text","color","type"]},"FlowGroup":{"type":"object","description":"用于组织流程模块的语义分组。不影响执行——模块仍位于流程中的原始位置。分组可在编辑器中提供命名和折叠功能。成员会根据 start_id 和 end_id 之间路径上的所有节点动态计算。","properties":{"summary":{"type":"string","description":"此分组的显示名称"},"note":{"type":"string","description":"显示在分组标题下方的 Markdown 备注"},"autocollapse":{"type":"boolean","default":false,"description":"如果为 true,此分组在流程编辑器中默认折叠。仅为 UI 提示。"},"start_id":{"type":"string","description":"此分组中第一个流程模块的 ID(拓扑入口点)"},"end_id":{"type":"string","description":"此分组中最后一个流程模块的 ID(拓扑出口点)"},"color":{"type":"string","description":"流程编辑器中分组的颜色,可选值:yellow、blue、green、purple、pink、orange、red、cyan、lime、gray。省略时由编辑器选择。"}},"required":["start_id","end_id"]},"RetryIf":{"type":"object","description":"根据错误或结果进行条件重试","properties":{"expr":{"type":"string","description":"返回 true 即重试的 JavaScript 表达式。可访问 'result' 和 'error' 变量"}},"required":["expr"]},"StopAfterIf":{"type":"object","description":"模块的提前终止条件","properties":{"skip_if_stopped":{"type":"boolean","description":"如果为 true,触发此条件时跳过后续步骤"},"expr":{"type":"string","description":"模块运行后求值的 JavaScript 表达式。可使用 'result'(步骤的结果)或 'flow_input'。返回 true 即停止"},"error_message":{"type":"string","nullable":true,"description":"因错误而停止时使用的自定义错误消息。不能与 skip_if_stopped 同时使用。如果设置为非空字符串,流程将以此错误停止。如果为空字符串,则使用默认错误消息。如果为 null 或省略,则不会引发错误。"},"error_include_result":{"type":"boolean","description":"因错误而停止时(已设置 error_message),将停止步骤自身的结果嵌入所引发的错误对象中(作为 error.result),而不是丢弃该结果。顶层结果仍为 { error }。默认为 false。"}},"required":["expr"]},"FlowModule":{"type":"object","description":"流程中的单个步骤。可以是脚本、子流程、循环或分支","properties":{"id":{"type":"string","description":"此步骤的唯一标识符。用于通过 'results.step_id' 引用结果。必须是有效标识符(字母数字、下划线、连字符)"},"value":{"$ref":"#/components/schemas/FlowModuleValue"},"stop_after_if":{"description":"此步骤完成后求值的提前终止条件","$ref":"#/components/schemas/StopAfterIf"},"stop_after_all_iters_if":{"description":"仅适用于循环——所有迭代完成后求值的提前终止条件","$ref":"#/components/schemas/StopAfterIf"},"skip_if":{"type":"object","description":"根据先前结果或流程输入,有条件地跳过此步骤","properties":{"expr":{"type":"string","description":"返回 true 即跳过的 JavaScript 表达式。可使用 'flow_input' 或 'results.<step_id>'"}},"required":["expr"]},"sleep":{"description":"执行此步骤前的延迟时间(秒),也可使用表达式","$ref":"#/components/schemas/InputTransform"},"cache_ttl":{"type":"number","description":"此步骤结果的缓存时长(秒)"},"cache_ignore_s3_path":{"type":"boolean"},"timeout":{"description":"最大执行时间(秒),可以是静态值或表达式","$ref":"#/components/schemas/InputTransform"},"delete_after_secs":{"type":"integer","description":"如果设置,则在作业完成后经过此秒数,删除步骤的参数、结果和日志"},"summary":{"type":"string","description":"简要说明此步骤的功能"},"mock":{"type":"object","description":"测试时不执行实际步骤所用的模拟配置","properties":{"enabled":{"type":"boolean","description":"如果为 true,则返回模拟值,而不是执行步骤"},"return_value":{"description":"模拟时返回的值"}}},"suspend":{"type":"object","description":"等待用户输入的审批/恢复步骤配置","properties":{"required_events":{"type":"integer","description":"继续前所需的审批数"},"timeout":{"type":"integer","description":"自动继续或取消前的超时时间(秒)"},"resume_form":{"type":"object","description":"恢复时收集输入的表单架构","properties":{"schema":{"type":"object","description":"恢复表单的 JSON Schema"}}},"user_auth_required":{"type":"boolean","description":"如果为 true,只有经过身份验证的用户才能审批"},"user_groups_required":{"description":"可进行审批的组的表达式或列表","$ref":"#/components/schemas/InputTransform"},"self_approval_disabled":{"type":"boolean","description":"如果为 true,启动流程的用户不能审批"},"hide_cancel":{"type":"boolean","description":"如果为 true,在审批表单上隐藏取消按钮"},"continue_on_disapprove_timeout":{"type":"boolean","description":"如果为 true,超时时继续流程,而不是取消"},"skin":{"type":"string","enum":["detailed","minimal"],"description":"审批请求在审批页面和 Slack/Teams 审批消息中的呈现方式。'detailed'(未设置时使用)显示流程详情(参数、图表、审批人);'minimal' 仅显示请求内容:步骤说明、表单以及批准/拒绝操作"}}},"priority":{"type":"number","description":"此步骤的执行优先级(数值越高,越先运行)"},"continue_on_error":{"type":"boolean","description":"如果为 true,即使此步骤失败,流程也会继续"},"job_token_scopes":{"type":"array","nullable":true,"items":{"type":"string"},"description":"此步骤作业所用令牌的权限范围限制(domain:action[:resource],例如 oidc:write),并叠加在流程自身的限制之上:步骤只能进一步缩小权限范围。适用于此步骤运行的所有内容(循环和分支主体、AI Agent 工具)。"},"retry":{"description":"此步骤失败时的重试配置","$ref":"#/components/schemas/Retry"},"debouncing":{"description":"此步骤的防抖配置(仅 EE 支持)","type":"object","properties":{"debounce_delay_s":{"type":"integer","description":"跨流程运行对步骤执行进行防抖的延迟时间(秒)"},"debounce_key":{"type":"string","description":"用于对防抖执行进行分组的表达式。支持 $workspace 和 $args[name]。默认值:$workspace/flow/<flow_path>-<step_id>"},"debounce_args_to_accumulate":{"type":"array","description":"在防抖执行期间累积的数组类型参数","items":{"type":"string"}},"max_total_debouncing_time":{"type":"integer","description":"强制执行前的最长总等待时间(秒)"},"max_total_debounces_amount":{"type":"integer","description":"强制执行前的最大防抖次数"}}}},"required":["value","id"]},"InputTransform":{"description":"映射步骤的输入参数。可以是静态值,也可以是引用先前结果或流程输入的 JavaScript 表达式","oneOf":[{"$ref":"#/components/schemas/StaticTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"StaticTransform":{"type":"object","description":"直接传递给步骤的静态值。用于硬编码值或 '$res:path/to/resource' 之类的资源引用","properties":{"value":{"description":"静态值。对于资源,使用 '$res:path/to/resource' 格式"},"type":{"type":"string","enum":["static"]}},"required":["type"]},"JavascriptTransform":{"type":"object","description":"在运行时求值的 JavaScript 表达式。可通过 'results.step_id' 引用先前步骤的结果,或通过 'flow_input.property' 引用流程输入。在 for 循环中,使用 'flow_input.iter.value' 获取当前迭代值(在 while 循环中,它等于 'flow_input.iter.index')","properties":{"expr":{"type":"string","description":"返回值的 JavaScript 表达式。可用变量:results(包含所有先前步骤结果的对象)、flow_input(流程输入)、flow_input.iter(在循环中)"},"type":{"type":"string","enum":["javascript"]}},"required":["expr","type"]},"AiTransform":{"type":"object","description":"由 AI 运行时解析此输入的值。AI 引擎会决定如何满足该参数。","properties":{"type":{"type":"string","enum":["ai"]}},"required":["type"]},"AIProviderKind":{"type":"string","description":"支持的 AI 提供商类型","enum":["openai","azure_openai","azure_foundry","anthropic","mistral","deepseek","googleai","groq","openrouter","togetherai","customai","aws_bedrock","typesafe","cloudflare"]},"ProviderConfig":{"type":"object","description":"完整的 AI 提供商配置,包含资源引用和模型选择","properties":{"kind":{"$ref":"#/components/schemas/AIProviderKind"},"resource":{"type":"string","description":"格式为 '$res:{resource_path}' 的资源引用,指向提供商凭据"},"model":{"type":"string","description":"模型标识符(例如 'gpt-4'、'claude-3-opus-20240229'、'gemini-pro')"},"reasoning_effort":{"type":"string","description":"支持扩展思考的模型所用的提供商原生推理强度令牌(例如 'low'、'high'、'none')。可选;未设置时使用提供商默认值。"}},"required":["kind","resource","model"]},"StaticProviderTransform":{"type":"object","description":"直接传递给 AI Agent 的静态提供商配置","properties":{"value":{"$ref":"#/components/schemas/ProviderConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"ProviderTransform":{"description":"提供商配置——可以是静态配置(ProviderConfig)、JavaScript 表达式,或由 AI 确定","oneOf":[{"$ref":"#/components/schemas/StaticProviderTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticProviderTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"MemoryOff":{"type":"object","description":"不使用对话记忆/上下文","properties":{"kind":{"type":"string","enum":["off"]}},"required":["kind"]},"MemoryWindow":{"type":"object","description":"保留由运行的 memory id(或步骤的\nmemory_id)指定的记忆中最近的消息。没有 memory id 时,Agent 不使用记忆。\n","properties":{"kind":{"type":"string","enum":["window"]},"context_length":{"type":"integer","description":"加载和存储的最近消息数量。0 表示关闭记忆。"}},"required":["kind","context_length"]},"MemoryAuto":{"type":"object","deprecated":true,"description":"已弃用,但仍按写入时的方式读取:使用运行的 memory id,否则使用此处的 memory_id。\n设置此类型时,不读取步骤自身的 memory_id;请切换为 window 类型以使用它。\n未设置 context_length 或其值为 0 时,记忆为 off,并读取 previous_messages。\n","properties":{"kind":{"type":"string","enum":["auto"]},"context_length":{"type":"integer","description":"在上下文中保留的最大消息数"},"memory_id":{"type":"string","description":"Agent 调用之间持久记忆的标识符"}},"required":["kind"]},"MemoryCompaction":{"type":"object","description":"保留由运行的 memory id(或步骤的 memory_id)指定的全部记忆;随着对话接近模型的上下文窗口,将较早的部分替换为摘要。\n没有 memory id 时,Agent 不使用记忆,压缩功能会限制此次运行自身的循环。\n","properties":{"kind":{"type":"string","enum":["compaction"]},"context_window":{"type":"integer","description":"以令牌数为单位,覆盖从模型查询到的上下文窗口。仅当 Windmill 不认识某个模型时需要设置;这类模型的默认值为 128000。\n"}},"required":["kind"]},"MemoryMessage":{"type":"object","description":"对话历史中的单条消息","properties":{"role":{"type":"string","enum":["user","assistant","system"]},"content":{"type":"string"}},"required":["role","content"]},"MemoryManual":{"type":"object","deprecated":true,"description":"已弃用,但仍按写入时的方式读取。请将步骤切换为 off,并使用 previous_messages。","properties":{"kind":{"type":"string","enum":["manual"]},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MemoryMessage"}}},"required":["kind","messages"]},"MemoryConfig":{"description":"由 Windmill 存储并在每次请求中重放的托管记忆。记忆由 memory id 命名,参见 memory_id。记忆关闭时,步骤可通过 previous_messages 提供历史记录。","oneOf":[{"$ref":"#/components/schemas/MemoryOff"},{"$ref":"#/components/schemas/MemoryWindow"},{"$ref":"#/components/schemas/MemoryCompaction"},{"$ref":"#/components/schemas/MemoryAuto"},{"$ref":"#/components/schemas/MemoryManual"}],"discriminator":{"propertyName":"kind","mapping":{"off":"#/components/schemas/MemoryOff","window":"#/components/schemas/MemoryWindow","compaction":"#/components/schemas/MemoryCompaction","auto":"#/components/schemas/MemoryAuto","manual":"#/components/schemas/MemoryManual"}}},"StaticMemoryTransform":{"type":"object","description":"直接传递给 AI Agent 的静态记忆配置","properties":{"value":{"$ref":"#/components/schemas/MemoryConfig"},"type":{"type":"string","enum":["static"]}},"required":["type","value"]},"MemoryTransform":{"description":"记忆配置——可以是静态配置(MemoryConfig)、JavaScript 表达式,或由 AI 确定","oneOf":[{"$ref":"#/components/schemas/StaticMemoryTransform"},{"$ref":"#/components/schemas/JavascriptTransform"},{"$ref":"#/components/schemas/AiTransform"}],"discriminator":{"propertyName":"type","mapping":{"static":"#/components/schemas/StaticMemoryTransform","javascript":"#/components/schemas/JavascriptTransform","ai":"#/components/schemas/AiTransform"}}},"FlowModuleValue":{"description":"流程步骤的实际实现。可以是脚本(内联或引用)、子流程、循环、分支或特殊模块类型","oneOf":[{"$ref":"#/components/schemas/RawScript"},{"$ref":"#/components/schemas/PathScript"},{"$ref":"#/components/schemas/PathFlow"},{"$ref":"#/components/schemas/ForloopFlow"},{"$ref":"#/components/schemas/WhileloopFlow"},{"$ref":"#/components/schemas/BranchOne"},{"$ref":"#/components/schemas/BranchAll"},{"$ref":"#/components/schemas/Identity"},{"$ref":"#/components/schemas/AiAgent"},{"$ref":"#/components/schemas/AiDecision"}],"discriminator":{"propertyName":"type","mapping":{"rawscript":"#/components/schemas/RawScript","script":"#/components/schemas/PathScript","flow":"#/components/schemas/PathFlow","forloopflow":"#/components/schemas/ForloopFlow","whileloopflow":"#/components/schemas/WhileloopFlow","branchone":"#/components/schemas/BranchOne","branchall":"#/components/schemas/BranchAll","identity":"#/components/schemas/Identity","aiagent":"#/components/schemas/AiAgent","aidecision":"#/components/schemas/AiDecision"}}},"RawScript":{"type":"object","description":"代码直接定义在流程中的内联脚本。未指定时默认使用 'bun' 作为语言。脚本通过 input_transforms 接收参数","properties":{"input_transforms":{"type":"object","description":"将参数名称映射到其值(静态值或 JavaScript 表达式)。这些值将成为脚本的输入参数","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"content":{"type":"string","description":"脚本源代码。应导出 'main' 函数"},"language":{"type":"string","description":"此脚本使用的编程语言","enum":["deno","bun","bunnative","python3","go","bash","powershell","postgresql","mysql","bigquery","snowflake","mssql","oracledb","graphql","nativets","php","rust","ansible","csharp","nu","java","ruby","rlang","duckdb"]},"path":{"type":"string","description":"保存此脚本时使用的可选路径"},"lock":{"type":"string","description":"依赖项的锁定文件内容"},"type":{"type":"string","enum":["rawscript"]},"tag":{"type":"string","description":"用于执行路由的工作器组标签"},"concurrent_limit":{"type":"number","description":"此脚本的最大并发执行数"},"concurrency_time_window_s":{"type":"number","description":"concurrent_limit 的时间窗口"},"custom_concurrency_key":{"type":"string","description":"用于分组并发执行的自定义键"},"is_trigger":{"type":"boolean","description":"如果为 true,此脚本是可启动流程的触发器"},"assets":{"type":"array","description":"此脚本访问的外部资源(S3 对象、资源等)","items":{"type":"object","required":["path","kind"],"properties":{"path":{"type":"string","description":"资源路径"},"kind":{"type":"string","description":"资源类型","enum":["s3object","resource","ducklake","datatable","volume","dbt"]},"access_type":{"type":"string","nullable":true,"description":"此资源的访问级别","enum":["r","w","rw"]},"alt_access_type":{"type":"string","nullable":true,"description":"替代访问级别","enum":["r","w","rw"]}}}}},"required":["type","content","language","input_transforms"]},"PathScript":{"type":"object","description":"通过路径引用现有脚本。调用先前保存的脚本时使用此项,而不是编写内联代码","properties":{"input_transforms":{"type":"object","description":"将参数名称映射到其值(静态值或 JavaScript 表达式)。这些值将成为脚本的输入参数","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"工作区中脚本的路径(例如 'f/scripts/send_email')"},"hash":{"type":"string","description":"要使用的脚本特定版本哈希(可选)"},"type":{"type":"string","enum":["script"]},"tag_override":{"type":"string","description":"覆盖脚本默认的工作器组标签"},"is_trigger":{"type":"boolean","description":"如果为 true,此脚本是可启动流程的触发器"}},"required":["type","path","input_transforms"]},"PathFlow":{"type":"object","description":"通过路径引用现有流程。调用另一个流程作为子流程时使用此项","properties":{"input_transforms":{"type":"object","description":"将参数名称映射到其值(静态值或 JavaScript 表达式)。这些值将成为子流程的输入参数","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}},"path":{"type":"string","description":"工作区中流程的路径(例如 'f/flows/process_user')"},"type":{"type":"string","enum":["flow"]}},"required":["type","path","input_transforms"]},"ForloopFlow":{"type":"object","description":"对迭代器进行循环,并执行嵌套模块。在循环内,使用 'flow_input.iter.value' 访问当前迭代值,使用 'flow_input.iter.index' 访问索引。支持并行执行,以提高 I/O 密集型操作的性能","properties":{"modules":{"type":"array","description":"每次迭代要执行的步骤。步骤可通过 'flow_input.iter.value' 引用迭代值","items":{"$ref":"#/components/schemas/FlowModule"}},"iterator":{"description":"返回要迭代数组的 JavaScript 表达式。可引用 'results.step_id' 或 'flow_input'","$ref":"#/components/schemas/InputTransform"},"skip_failures":{"type":"boolean","description":"如果为 true,迭代失败不会停止循环。失败的迭代返回 null"},"type":{"type":"string","enum":["forloopflow"]},"parallel":{"type":"boolean","description":"如果为 true,迭代将并发运行(对 I/O 密集型操作更快)。与 parallelism 配合使用以控制并发数"},"parallelism":{"description":"parallel=true 时的最大并发迭代数。用于限制资源使用量。可以是静态数值或表达式","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","iterator","skip_failures","type"]},"WhileloopFlow":{"type":"object","description":"重复执行嵌套模块,直到停止。隐式迭代器是迭代计数器,因此 'flow_input.iter.value' 等于 'flow_input.iter.index'(0、1、2……),且不会携带状态。要在迭代间传递状态,步骤应通过 'results.<its_own_id>' 读取自身上一次迭代的结果,并提供首次迭代的回退值——此时循环的 stop_after_if 必须设置在该内部步骤上(如果主体是普通的单步骤,且 stop_after_if 设置在循环模块上,则 'results' 不会跨迭代解析,因此永远不会终止);普通计数器则可通过 'flow_input.iter.index' 推导,该方式适用于所有配置。stop_after_if 在每次迭代后求值——在循环模块上,'result' 是最后一次迭代的结果","properties":{"modules":{"type":"array","description":"每次迭代要执行的步骤","items":{"$ref":"#/components/schemas/FlowModule"}},"skip_failures":{"type":"boolean","description":"如果为 true,迭代失败不会停止循环。失败的迭代返回 null"},"type":{"type":"string","enum":["whileloopflow"]},"parallel":{"type":"boolean","description":"如果为 true,迭代将并发运行(在 while 循环中请谨慎使用)"},"parallelism":{"description":"parallel=true 时的最大并发迭代数","$ref":"#/components/schemas/InputTransform"},"squash":{"type":"boolean"}},"required":["modules","skip_failures","type"]},"BranchOne":{"type":"object","description":"条件分支,仅执行第一个匹配的分支。按顺序评估分支,并运行第一个表达式为 true 的分支。如果没有分支匹配,则运行默认分支","properties":{"branches":{"type":"array","description":"按顺序评估的分支数组。第一个 expr 求值为 true 的分支将执行","items":{"type":"object","properties":{"summary":{"type":"string","description":"此分支条件的简要说明"},"expr":{"type":"string","description":"返回布尔值的 JavaScript 表达式。可使用 'results.step_id' 或 'flow_input'。第一个为 true 的 expr 生效"},"modules":{"type":"array","description":"此分支 expr 为 true 时要执行的步骤","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules","expr"]}},"default":{"type":"array","description":"没有分支表达式匹配时要执行的步骤","items":{"$ref":"#/components/schemas/FlowModule"}},"type":{"type":"string","enum":["branchone"]}},"required":["branches","default","type"]},"BranchAll":{"type":"object","description":"并行分支,同时执行所有分支。与 BranchOne 不同,所有分支都会运行,不受条件限制。适用于并发执行独立任务","properties":{"branches":{"type":"array","description":"全部执行的分支数组(并行或顺序执行)","items":{"type":"object","properties":{"summary":{"type":"string","description":"此分支用途的简要说明"},"skip_failure":{"type":"boolean","description":"如果为 true,此分支失败不会导致整个流程失败"},"modules":{"type":"array","description":"此分支中要执行的步骤","items":{"$ref":"#/components/schemas/FlowModule"}}},"required":["modules"]}},"type":{"type":"string","enum":["branchall"]},"parallel":{"type":"boolean","description":"如果为 true,所有分支将并发执行;如果为 false,则按顺序执行"}},"required":["branches","type"]},"AgentTool":{"type":"object","description":"AI Agent 可用的工具。可以是流程模块,也可以是外部 MCP(Model Context Protocol)工具","properties":{"id":{"type":"string","description":"此工具的唯一标识符。不能包含空格——请改用下划线(例如 'get_user_data',而不是 'get user data')"},"summary":{"type":"string","description":"AI Agent 调用此工具时使用的名称,而不是面向人的标签。对于 flowmodule 工具,必须匹配 ^[a-zA-Z0-9_]+$——仅限字母、数字和下划线(例如 'search_documentation',而不是 'Search documentation'),且必须设置;对于 mcp、websearch 或 question 工具,则是普通标签。请将面向用户的说明写在 'description' 中。"},"description":{"type":"string","description":"提供给 AI 的工具自由文本说明,用于判断何时以及如何调用。覆盖从底层脚本自动派生的说明。"},"job_token_scopes":{"type":"array","nullable":true,"items":{"type":"string"},"description":"对此工具作业所用令牌的权限范围限制,并叠加在 Agent 步骤自身的限制之上:工具只能进一步缩小权限范围。"},"requires_approval":{"type":"boolean","description":"仅适用于 flowmodule 和 mcp 工具。每次调用都会等待用户在流程运行页面或聊天中批准或拒绝;期间 Agent 不占用工作器。同一轮中不需要审批的调用会立即运行。若调用被拒绝,或 30 分钟内无人响应,系统会告知 AI 此调用未运行。"},"value":{"$ref":"#/components/schemas/ToolValue"}},"required":["id","value"]},"ToolValue":{"description":"工具的实现。可以是流程模块(脚本/流程)或 MCP 工具引用","oneOf":[{"$ref":"#/components/schemas/FlowModuleTool"},{"$ref":"#/components/schemas/McpToolValue"},{"$ref":"#/components/schemas/WebsearchToolValue"},{"$ref":"#/components/schemas/QuestionToolValue"}],"discriminator":{"propertyName":"tool_type","mapping":{"flowmodule":"#/components/schemas/FlowModuleTool","mcp":"#/components/schemas/McpToolValue","websearch":"#/components/schemas/WebsearchToolValue","question":"#/components/schemas/QuestionToolValue"}}},"FlowModuleTool":{"description":"以流程模块(脚本、流程等)实现的工具。AI 可以像调用其他流程模块一样调用它","allOf":[{"type":"object","properties":{"tool_type":{"type":"string","enum":["flowmodule"]}},"required":["tool_type"]},{"$ref":"#/components/schemas/FlowModuleValue"}]},"WebsearchToolValue":{"type":"object","description":"以网页搜索工具实现的工具。AI 可以像调用其他网页搜索工具一样调用它","properties":{"tool_type":{"type":"string","enum":["websearch"]}},"required":["tool_type"]},"QuestionToolValue":{"type":"object","description":"内置工具,AI 通过 ask_question 调用:Agent 停止并释放其工作器,直到有人在流程运行页面或聊天中回答,或问题超时;随后 Agent 会恢复运行,并将答案作为工具结果。","properties":{"tool_type":{"type":"string","enum":["question"]},"timeout":{"type":"integer","description":"等待答案的秒数。超时后,系统会告知 AI 问题已超时。默认值为 1800。"}},"required":["tool_type"]},"McpToolValue":{"type":"object","description":"对外部 MCP(Model Context Protocol)工具的引用。AI 可以调用 MCP 服务中的工具","properties":{"tool_type":{"type":"string","enum":["mcp"]},"resource_path":{"type":"string","description":"MCP 资源/服务配置的路径"},"include_tools":{"type":"array","description":"要从此 MCP 服务中包含的特定工具白名单","items":{"type":"string"}},"exclude_tools":{"type":"array","description":"要从此 MCP 服务中排除的工具黑名单","items":{"type":"string"}}},"required":["tool_type","resource_path"]},"AiAgent":{"type":"object","description":"可使用工具完成任务的 AI Agent 步骤。Agent 接收输入,并可调用任意已配置工具来完成任务","properties":{"input_transforms":{"type":"object","description":"映射到对应值的 AI Agent 输入参数","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"output_type":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"输出格式类型。\n有效值:'text'(默认值)——纯文本响应;'image'——图像生成\n"},"user_message":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"提供给 AI Agent 的用户提示词/消息。支持使用\nflow.input 语法进行变量插值。除非记忆关闭且 previous_messages 提供\n提示词,否则此项为必填;图像输出始终需要此项。\n"},"system_prompt":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"指导 AI 行为、角色设定和响应风格的系统指令。可选。"},"streaming":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"布尔值。如果为 true,则逐步流式传输 AI 响应。\n流式事件包括:token_delta、reasoning_token_delta、tool_call、tool_call_arguments、tool_execution、tool_result\n"},"memory":{"$ref":"#/components/schemas/MemoryTransform"},"memory_id":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"字符串。指定此步骤读取和写入的记忆,覆盖启动运行时使用的 memory id\n(聊天对话、应用聊天会话或 memory_id 运行参数)。留空则使用运行的\nmemory id。固定值会在所有运行间共享一份记忆;例如 flow_input.customer_id\n这样的表达式则会为每个键分别保留一份记忆。如果求值为空,Agent 不使用记忆。\n仅当 memory 为 window 或 compaction 时读取:记忆关闭时会忽略此项;较旧的\nauto 或 manual 记忆类型均不会读取任何历史输入。\n"},"previous_messages":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"MemoryMessage 数组。由流程提供的历史记录,位于系统提示词\n和用户消息之间发送。仅当 memory 为 off 或未设置时读取:托管记忆\n会忽略此项,较旧的 auto 或 manual 记忆类型均不会读取任何历史输入。\n"},"output_schema":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"定义结构化输出格式的 JSON Schema 对象。当需要 AI 按特定结构返回数据时使用。\n支持标准 JSON Schema 属性:type、properties、required、items、enum、pattern、minLength、maxLength、minimum、maximum 等。\n示例:{ type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } }, required: ['name'] }\n"},"user_attachments":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"供 AI Agent 使用的文件引用数组(图像或 PDF)。\n格式:Array<{ bucket: string, key: string }>——S3 对象引用\n示例:[{ bucket: 'my-bucket', key: 'documents/report.pdf' }]\n"},"enabled_tools":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"指定此运行中 Agent 可以调用的 tools 中工具名称的字符串数组\n未设置时包含所有工具;空数组表示不包含任何工具。\n工具名称采用模型所见的名称。如果模型看不到某个工具的名称,则使用标识它的内容:\nMCP 服务使用其资源路径,并包含它提供的全部工具(具体包含哪些工具仍由该条目的\ninclude_tools/exclude_tools 决定);网页搜索条目使用保留名称 '__wm_web_search',\n无论其 summary 设置为何(任何工具都不能使用该名称)。\n示例:['get_user', 'u/admin/github_mcp', '__wm_web_search']\n"},"max_completion_tokens":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"整数。AI 在响应中生成的最大令牌数。\n范围:1 到 4,294,967,295。常见用途的典型值:256-4096。\n"},"temperature":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"浮点数。控制响应的随机性/创造性。\n范围:0.0 到 2.0(因提供商而异)\n- 0.0 = 确定、聚焦的响应\n- 0.7 = 均衡(常见默认值)\n- 1.0+ = 更有创造性/随机性\n"},"max_iterations":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"数值。限制 Agent 进行推理和使用工具的循环次数。\n范围:1-1000。\n"},"model":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"字符串。仅适用于关联到已保存 Agent 的步骤(参见 agent):替换该步骤每次运行时\nAgent 中保存的模型,但仍使用 Agent 自身的提供商资源。空值表示保留已保存的模型。\n设置为 flow_input.model 这样的表达式,即可按运行选择模型。\n"},"reasoning_effort":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"字符串。仅适用于关联到已保存 Agent 的步骤(参见 agent):以提供商原生令牌\n(例如 'low'、'high'、'none')替换该步骤每次运行时 Agent 中保存的推理强度。\n空值表示不发送推理强度,因此模型使用默认值。省略此输入则保留已保存的推理强度,\n但仅当步骤运行已保存的模型时才会保留。未关联到 Agent 的步骤应在 provider\n中同时设置模型和推理强度。\n"}}},"tools":{"type":"array","description":"Agent 可使用的工具数组。Agent 会根据任务决定调用哪些工具","items":{"$ref":"#/components/schemas/AgentTool"}},"type":{"type":"string","enum":["aiagent"]},"tag":{"type":"string","description":"用于执行路由的工作器组标签。未设置时,AI Agent 步骤会使用流程标签(默认值为 flow)"},"omit_output_from_conversation":{"type":"boolean","default":false,"description":"如果为 true,启用聊天模式时,此 AI Agent 步骤不会将其助手消息或工具消息保存到流程对话中。"},"agent":{"type":"string","description":"可复用 ai_agent 资源的路径(混合关联)。设置后,运行时会从该资源解析 Agent 核心配置\n(提供商/模型/系统提示词等)和工具集;模块的 input_transforms 随后仅承载\n流程本地输入(user_message、user_attachments、enabled_tools 以及历史输入 memory_id\n和 previous_messages),也可选择覆盖 Agent 中保存的 model 和 reasoning_effort。\n"},"tool_inputs":{"type":"object","description":"Agent 工具输入的本地接线配置,按工具 ID 和输入键索引。将所引用 Agent 的工具绑定到\n此流程上下文(flow_input/results),而不修改共享资源;运行时会将其覆盖到工具的\ninput_transforms 上——即使未设置 agent 也会如此,因为为了编辑而分离的步骤会保留\n这些覆盖项,直到将其保存回资源或取消关联。\n","additionalProperties":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/InputTransform"}}},"parallel":{"type":"boolean","description":"如果为 true,Agent 可以并行执行多个工具调用"}},"required":["type","input_transforms"]},"AiDecision":{"type":"object","description":"AI 决策步骤:调用一次决策模型(TypeSafe 的 Jev、Cloudflare 兼容 Jev 的 Clef,或 OpenAI 的 Decisions API),对状态提出的类型化问题给出经过校准的概率,而不是生成文本。\n对于分类、路由、评分或检查等任务,优先使用此步骤而非 AI Agent:它速度快、成本低,且答案为结构化格式。其结果为 { output: { : answer },\nmodel, usage },因此后续步骤可读取例如 results..output.intent.choice 的值。要根据答案进行路由,请在其后接一个 branchone,并让分支 expr 读取\nresults..output..choice。也可用作 AI Agent 工具,由调用它的 Agent 提供状态。\n","properties":{"input_transforms":{"type":"object","description":"映射到对应值的决策输入参数","properties":{"provider":{"$ref":"#/components/schemas/ProviderTransform"},"state":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"问题所针对的内容:字符串、对象或字符串数组。\n包含描述性键且仅包含问题所需内容的对象效果最佳。\n示例:{ message: flow_input.message, plan: results.a.plan }\n"},"questions":{"allOf":[{"$ref":"#/components/schemas/InputTransform"}],"description":"将每个问题名称映射到 { type, instructions, criteria } 的对象,其中 type 可以是:\n- 'choice':criteria 将每个选项映射到其说明(最多 255 项);答案包含 choice、probabilities 和 confidence\n- 'score':criteria 是包含 2 到 10 个级别说明的有序数组;答案包含 score、legend、probabilities 和 confidence\n- 'noul':是/否;criteria 可选地包含 { true, false } 说明;答案包含 noul,即回答“是”的概率(0 到 1)\n示例:{ intent: { type: 'choice', instructions: '客户想要什么?',\ncriteria: { refund: '退款', bug: '出现故障' } } }\n"}},"required":["provider","state","questions"]},"type":{"type":"string","enum":["aidecision"]},"tag":{"type":"string","description":"用于执行路由的工作器组标签。未设置时,此步骤使用流程标签(默认值为 flow)"}},"required":["type","input_transforms"]},"Identity":{"type":"object","description":"直通模块,原样返回输入。适用于流程结构或作为占位符","properties":{"type":{"type":"string","enum":["identity"]},"flow":{"type":"boolean","description":"如果为 true,将此模块标记为流程身份模块(特殊处理)"}},"required":["type"]},"FlowStatus":{"type":"object","properties":{"step":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/FlowStatusModule"}},"user_states":{"additionalProperties":true},"preprocessor_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"}]},"failure_module":{"allOf":[{"$ref":"#/components/schemas/FlowStatusModule"},{"type":"object","properties":{"parent_module":{"type":"string"}}}]},"retry":{"type":"object","properties":{"fail_count":{"type":"integer"},"failed_jobs":{"type":"array","items":{"type":"string","format":"uuid"}}}}},"required":["step","modules","failure_module"]},"FlowStatusModule":{"type":"object","properties":{"type":{"type":"string","enum":["WaitingForPriorSteps","WaitingForEvents","WaitingForExecutor","InProgress","Success","Failure"]},"id":{"type":"string"},"job":{"type":"string","format":"uuid"},"count":{"type":"integer"},"progress":{"type":"integer"},"iterator":{"type":"object","properties":{"index":{"type":"integer"},"itered":{"type":"array","items":{}},"itered_len":{"type":"integer"},"args":{}}},"flow_jobs":{"type":"array","items":{"type":"string"}},"flow_jobs_success":{"type":"array","items":{"type":"boolean"}},"flow_jobs_duration":{"type":"object","properties":{"started_at":{"type":"array","items":{"type":"string"}},"duration_ms":{"type":"array","items":{"type":"integer"}}}},"branch_chosen":{"type":"object","properties":{"type":{"type":"string","enum":["branch","default"]},"branch":{"type":"integer"}},"required":["type"]},"branchall":{"type":"object","properties":{"branch":{"type":"integer"},"len":{"type":"integer"}},"required":["branch","len"]},"approvers":{"type":"array","items":{"type":"object","properties":{"resume_id":{"type":"integer"},"approver":{"type":"string"}},"required":["resume_id","approver"]}},"failed_retries":{"type":"array","items":{"type":"string","format":"uuid"}},"skipped":{"type":"boolean"},"agent_actions":{"type":"array","items":{"type":"object","oneOf":[{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"type":{"type":"string","enum":["tool_call"]},"module_id":{"type":"string"}},"required":["job_id","function_name","type","module_id"]},{"type":"object","properties":{"call_id":{"type":"string","format":"uuid"},"function_name":{"type":"string"},"resource_path":{"type":"string"},"type":{"type":"string","enum":["mcp_tool_call"]},"arguments":{"type":"object"}},"required":["call_id","function_name","resource_path","type"]},{"type":"object","properties":{"type":{"type":"string","enum":["web_search"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["approval"]},"call_id":{"type":"string"},"calls":{"type":"array","items":{"type":"object","properties":{"function_name":{"type":"string"},"arguments":{}},"required":["function_name","arguments"]}},"expires_at":{"type":"string","format":"date-time"}},"required":["type","call_id","calls","expires_at"]},{"type":"object","properties":{"type":{"type":"string","enum":["question"]},"call_id":{"type":"string"},"question":{"type":"string"},"choices":{"type":"array","items":{"type":"string"},"description":"模型提出的答案选项;始终可以使用自由文本作答"},"multi_select":{"type":"boolean","description":"是否可以同时选择多个选项"},"expires_at":{"type":"string","format":"date-time"}},"required":["type","call_id","question","expires_at"]},{"type":"object","properties":{"type":{"type":"string","enum":["message"]}},"required":["content","type"]}]}},"agent_actions_success":{"type":"array","items":{"type":"boolean"}}},"required":["type"]}}