工作流
🔵 工作流——基于智能体和工具的声明式多步骤流水线。每个工作流都是 config/workflows/ 中的一个 YAML 文件,描述一个有序的步骤列表,这些步骤操作一个不断累积的变量作用域(payload input 加上每个步骤的 output_key)。
步骤支持热重载;通过 POST /v1/workflows/<name>/run 启动。
12.1 步骤类型
type |
用途 |
|---|---|
agent |
使用模板化 prompt 运行模板(或内联 instructions);可选地通过 output_schema 获得结构化结果 |
tool |
使用模板化的 args 直接调用工具 |
saga |
带补偿的有序工具组:当某步骤失败时,已执行的步骤按相反顺序回滚 |
condition |
根据模板化的布尔表达式跳转到步骤 id |
for_each |
遍历集合,对每个元素执行嵌套步骤列表 |
loop |
重复嵌套块直到满足 until(或 max_iterations)——智能体内部循环 |
verify |
检查条件(工具和/或评判智能体)→ { passed, checks },步骤不会失败 |
模板使用 MiniJinja({{ var }}、{% if %}),从变量作用域渲染;{{ x | tojson }} 嵌入结构化值,env_var('NAME') 读取环境变量。
12.2 示例
name: triage
description: Просмотреть входящие коммиты и завести тикеты.
steps:
- id: fetch
type: tool
tool: git_commits
args: { since: "{{ input.since | default('yesterday') }}" }
output_key: commits
- id: process
type: for_each
over: commits.items # dot-path в скоуп переменных → массив
item: commit # переменная цикла
concurrency: 3
max_items: 100
steps:
- id: analyze
type: agent
template: review
prompt: "Отревьюй {{ commit.sha }}: {{ commit.message }}"
output_key: review
output_schema:
type: object
properties:
needs_action: { type: boolean }
summary: { type: string }
required: [needs_action, summary]
retry: { max: 2, backoff_secs: 5 }
timeout_secs: 120
- id: branch
type: condition
when: "{{ review.needs_action }}"
then: file_ticket
- id: file_ticket
type: tool
tool: send_report
args: { body: "{{ review.summary }}" }
collect:
reviews: "{{ results | tojson }}"
- id: done
type: tool
tool: send_report
args: { body: "{{ reviews }}" }
12.3 步骤参考
agent
| 字段 | 描述 |
|---|---|
template |
要运行的模板(与 instructions 互斥) |
instructions |
不使用模板的内联系统提示 |
tools |
额外的工具名称(与模板中的合并) |
prompt |
用户消息(MiniJinja) |
output_key |
将结果保存到作用域中的位置 |
output_schema |
JSON Schema——智能体通过 job_done 结束;经过验证的 payload 成为 output_key 的值 |
session |
有状态会话的键(同一次运行中具有相同键的步骤共享上下文) |
retry |
{ max, backoff_secs }——出错时重试整个步骤 |
timeout_secs |
单次运行的时限 |
budget |
token 限制 { max_per_turn, max_per_minute, max_total };对于内联 instructions 会替换默认限制(4000 / 50000 / 200000);对于 template 则被忽略 |
next |
显式指定下一步骤的 id(见 §12.9) |
没有 output_schema 时,步骤的输出是智能体最终回答的文本。有它时,如果智能体未返回有效的结构化结果,步骤就会失败。
tool
| 字段 | 描述 |
|---|---|
tool |
已注册工具的名称 |
args |
参数(字符串叶子节点为 MiniJinja;渲染为 JSON 的叶子节点以结构体形式传递) |
output_key |
保存结果的位置——如果有工具的 data 则用它,否则用 content |
idempotency_key |
用于有副作用工具的幂等键(见 05-tools-core 中的 idempotency:) |
next |
显式指定下一步骤的 id |
condition
| 字段 | 描述 |
|---|---|
when |
MiniJinja 表达式 → true/false |
then |
为真时的步骤 id |
else |
为假时的步骤 id |
for_each
| 字段 | 描述 |
|---|---|
over |
作用域中的 dot-path → 数组(例如 commits.items) |
item |
循环变量的名称(默认为 item) |
concurrency |
最大并行迭代数(默认为 1) |
max_items |
元素的硬性上限 |
steps |
嵌套子工作流,对每个元素运行一次 |
collect |
名称 → 模板的映射,将每个元素的结果(可作为 results 访问)折叠回父作用域 |
next |
显式指定下一步骤的 id |
每次迭代都基于父作用域的副本 + item 运行。其输出成为 results(数组)中每个元素的结果,collect 可以对其重新格式化。
saga
带补偿的有序有副作用工具组:每个正向步骤都可以声明 compensate,当某步骤失败时,已执行的步骤按相反顺序回滚。步骤会持久化到 durable outbox,因此挂起的 saga 可以经受重启。
步骤的值是正向步骤结果的数组。
| 字段 | 描述 |
|---|---|
saga_id |
saga 的 id(默认为步骤的 id);持久化状态的键 |
steps |
{ tool, args, idempotency_key?, max_attempts?, compensate? } 的列表 |
steps[].compensate |
{ tool, args }——回滚时的补偿调用 |
output_key |
将结果数组放到哪里 |
next |
显式指定下一步骤的 id |
steps:
- id: checkout
type: saga
saga_id: order-{{ input.order_id }}
output_key: checkout
steps:
- tool: reserve_stock
args: { sku: "{{ input.sku }}", qty: 1 }
compensate: { tool: release_stock, args: { sku: "{{ input.sku }}", qty: 1 } }
- tool: charge_card
args: { amount: "{{ input.amount }}", key: "{{ input.order_id }}" }
当 charge_card 失败时,内核会调用 release_stock 并将该步骤标记为 compensated;saga 步骤将以错误结束(saga '…' failed and was rolled back)。
loop
内部循环:重复 body,直到 until 渲染为 true,但不超过 max_iterations。
| 字段 | 描述 |
|---|---|
max_iterations |
迭代次数的硬性上限(必填) |
body |
嵌套步骤列表,每次迭代都会执行 |
until |
MiniJinja 表达式 → true/false;true 停止循环(成功) |
on_max |
当 max_iterations 耗尽时该怎么做:fail(默认)或 success |
iteration |
计数器变量的名称(从 0 开始),默认为 iteration |
output_key |
将 { iterations, stopped, max_iterations } 放到哪里 |
next |
显式指定下一步骤的 id |
body 的输出会累积在循环作用域中,并在结束后转移到父作用域(因此每次迭代后 until 都能看到最新的 verification)。
steps:
- id: fix
type: loop
max_iterations: 8
until: "{{ verification.passed }}"
on_max: fail
body:
- id: act
type: agent
template: bugfix-agent
prompt: "Исправь дефект (попытка {{ iteration }}): {{ input.summary }}"
output_key: attempt
- id: check
type: verify
tool: run_tests
args: { cmd: "cargo test" }
output_key: verification
verify
检查一个或多个条件。与其他步骤不同,检查不通过不是步骤错误:会返回 { "passed": false, "checks": [...] },以便可以对其分支(例如在循环的 until 中)。
| 字段 | 描述 |
|---|---|
checks |
检查列表:{ type: tool, tool, args, expect } 或 { type: agent, template/instructions, prompt, output_schema } |
require |
all(默认)或 any |
output_key |
将结果放到哪里 { passed, checks } |
next |
下一步的显式 id |
对于工具检查 expect 描述了如何读取结果:没有
expect 时取工具的 success;否则取 pointer(data 中的 JSON Pointer,
否则取解析后的 content)加上 equals/truthy。代理检查必须
返回结构化的 { "passed": bool }。
- id: v
type: verify
require: all
output_key: verification
checks:
- type: tool
tool: run_tests
args: { cmd: "cargo test" }
expect: { pointer: /passed, equals: true }
- type: agent
template: reviewer
prompt: "Проверь изменения: {{ diff }}"
output_schema:
type: object
properties: { passed: { type: boolean }, reason: { type: string } }
required: [passed]
12.4 访问控制
工作流可以声明它涉及的模板——那么 限定范围操作员 只有在这些模板位于其范围内时才会启动它:
name: reports
templates: [rss-agent, summarizer] # шаблоны, которые воркфлоу может трогать
steps: [ ... ]
Scope::All(完全管理员)可以启动任何工作流。- 限定范围操作员(
auth.admin_tokens中的templates: [...])启动工作流 仅当其所有templates都在其范围内,并且只能看到这样的工作流。 - 没有
templates的工作流——仅限管理员。
run / get_run 对超出其范围的限定范围操作员返回 403;list
不会显示它们。
12.5 触发器
工作流可以无需显式 API 调用而启动:
name: reports
triggers:
cron:
- schedule: "0 0 9 * * *" # cron: sec min hour dom month dow (UTC)
input: { topic: daily } # фиксированный input (по умолчанию {})
webhook:
token_env: WF_REPORTS_TOKEN # опциональный bearer (рекомендуется)
steps: [ ... ]
| 触发器 | 描述 |
|---|---|
cron[].schedule |
cron 表达式(6 个字段,秒级分辨率) |
cron[].input |
每次运行固定的 input |
webhook |
启用 POST /v1/workflows/<name>/webhook;JSON 正文成为 input |
webhook.token_env |
带 bearer 令牌的 env;未设置/为空 → 所有请求被拒绝 |
12.6 HTTP API
| Endpoint | Auth | 描述 |
|---|---|---|
GET /v1/workflows |
管理员 | 工作流列表 + 最近运行(?runs=N) |
POST /v1/workflows/<name>/run |
管理员 | 启动;JSON 正文成为 input。返回 { ok, id } |
POST /v1/workflows/<name>/webhook |
自己的令牌 | 通过 webhook 启动(JSON 正文 → input) |
GET /v1/workflows/runs/<id> |
管理员 | 运行状态 + 步骤历史 |
POST /v1/workflows/runs/<id>/cancel |
管理员 | 在下一个步骤边界停止运行(状态 cancelled,日志被移除) |
GET /workflows |
管理员 | HTML 页面(启动 + 查看) |
运行写入 logs/workflows.db(workflow_runs);状态 running →
success / error,带有 per-step 记录(id、status、output、error、retries、
timings)。重启之间的状态不会持久化——只有运行历史。
12.7 从代理和工具启动
工作流可以通过 workflow_tool 从另一个代理启动:
kind: workflow_tool
name: run_triage
description: Запустить воркфлоу триажа.
workflow: triage # дефолтный воркфлоу (перекрывается аргументом `workflow`)
参数:workflow(名称,配置时可选)和 input(对象)。
工具返回 run id;启动异步继续。
12.8 本地测试
agent-os module workflow run config/workflows/smoke.yaml '{"items":["Cargo.toml","README.md"]}'
# или input из файла (удобно на Windows):
agent-os module workflow run config/workflows/agent_demo.yaml --args-file args.json
tool-步骤使用内置 + 来自 config/agents/ 的 YAML 工具;
agent-步骤使用来自 config/server.yaml 的提供者(或
DEEPSEEK_API_KEY)。从仓库根目录运行,以便 config/ 能够解析。
12.9 备注
- 控制流。 步骤按顺序执行;
condition跳转到 idthen/else,执行从那里继续。带有next的步骤跳转到它 而不是列表中的下一个——使用next来关闭if-分支并 跳过else。跳转受到无限循环保护。 - 对空数组执行
for_each——no-op(步骤成功并带有[])。 - 失败的步骤会使整个运行失败;
retry应用于此之前的步骤。 - 有状态会话。 给两个或更多
agent-步骤一个键session——它们 将共享同一个对话:第一个创建会话(惰性),后续的 复用它。会话在包围作用域结束时关闭。 每次for_each迭代都会按 key 获得自己的会话。 - 步骤
agent的output_schema复用与代理相同的结构化 完成契约(见 §4):schema 替换job_done参数,而 经过验证的参数成为结果。
12.10 持久性(durable)
默认情况下,运行状态只存在于内存中:重启时未完成的
运行会丢失(历史中保留 running)。标志 durable: true 启用
运行日志——引擎在每个步骤边界提交 scope 和位置
(对于 loop——在每次迭代时),并且在服务器启动时未完成的运行
从最后一个边界重新启动(logs/workflows.db,表
workflow_run_state)。
name: long-harness
durable: true
budget: # опциональный бюджет на весь запуск
max_per_turn: 8000
max_per_minute: 60000
max_total: 500000
steps: [ ... ]
语义是 at-least-once(与 durable 会话和 outbox 一样):未完成的步骤
或迭代会整体重放,因此步骤的副作用必须是
幂等的(tool-步骤的 idempotency_key 字段或 saga)。loop 内部的嵌套步骤
从当前迭代的开头重新执行,而 for_each——仅从
未完成的元素开始(已完成的元素取自日志,不会
重放)。
budget设置所有 代理步骤使用的总组wf:<run_id>的限制;其已消耗总量会进入日志,因此 重启后限制不会重置。durable-运行始终获得这样的组 (如果没有设置budget,则无限制)——用于成本(token)核算。- 幂等性。 对于
durable-运行,没有显式idempotency_key的tool-步骤会获得稳定的键wf:<run_id>:<шаг>:<итерация>: 在重放边界时,重复的副作用会被 side-effect 存储去重(需要logging.side_effect_db_path/outbox——否则 去重只存在于内存中)。显式idempotency_key优先。 - 运行截止时间。
timeout_secs限制整个运行的时间:在 在步骤边界处,运行以状态timeout停止。绝对截止时间从日志中的启动时间恢复,因此重启不会延长限制。 - 超过恢复尝试限制(3 次)的运行会被标记为
error(“resume limit exceeded”),并且不再重放——这是对“有毒”运行的隔离。 - 需要启用
logging.workflow_db_path(默认logs/workflows.db)。
12.11 测试框架(隔离工作区)
harness: 块将工作流转变为“内部测试框架”——在隔离工作目录中带有循环(loop/verify)的持久化运行。示例:修复工单中的缺陷。
name: jira-bugfix
durable: true
harness:
workspace:
kind: git_worktree # или dir
repo: "."
base: origin/main
cleanup: on_success # on_success (по умолчанию) | always | never
triggers:
webhook: { token_env: JIRA_HOOK_TOKEN }
steps:
- id: fix
type: loop
max_iterations: 8
until: "{{ verification.passed }}"
body:
- { id: act, type: agent, template: bugfix-agent,
prompt: "Исправь: {{ input.summary }}", session: fix }
- { id: check, type: verify, tool: run_tests,
args: { cmd: "cargo test" }, output_key: verification }
- 引擎在启动时准备工作区,并将变量
workspace({ path, kind, branch, repo })放入作用域——步骤通过{{ workspace.path }}引用它。 git_worktree:从base分出单独分支harness/<run_id>;代理的修改被隔离并显示为 diff,分支在清理后作为产物保留。cleanup:on_success在成功时删除,always总是删除,never保留以供分析。- 工作区绑定到持久化日志:重启后运行会重新连接同一目录/工作树(
prepare是幂等的)。 - 工作区工具。 工作目录路径作为
template_vars[code_root]传递给代理步骤,因此code模块的工具(read_file、write_file、edit_file、list_files、grep、run_command)在隔离工作区内部工作,而不是在服务器的 cwd 中。 - 产物:在清理之前,引擎从工作区收集
git status、git diff --stat和git diff(对于 git 工作树)并放入 read-model;/harness显示运行的 diff。 - 输入/输出产物。 在
harness:块中可以声明式地声明sources(在启动时从input+workspace渲染)、results(在结束时——还从步骤的output_key渲染)和materials(内联内容)。产物具有{ kind, title, url?, content? };空渲染(url/title/content为空)会被丢弃。UI(/harness)渲染三个块——来源 / 结果 / 材料——以通用方式:url→ 链接,否则title可复制,content→ 可折叠块。来自 provisioner 的 captured diff/status 会自动进入materials。 - 运行通知。 在测试框架定义(
config/harnesses/<id>.yaml)中,notify:块向任意渠道发送事件消息(通用——与 cron 相同的 sink):events选择发送什么(started|finished|failed,默认[finished, failed]),而sinks选择发往哪里(格式CronSink:yandex_messenger、telegram、vk、email、webhook、channel、push……)。投递通过共享的 outbound-fan-out 进行;消息由运行的产物组装而成(源任务、状态、token、MR 链接)。 - 可观测性:页面
/harness和 APIGET /v1/harness/runs、GET /v1/harness/runs/:id(模块harness、logs/harness.db)。运行详情显示步骤/迭代时间线(来自 workflow 事件)、成本(token)、迭代次数、工作区和产物。 - 汇总:
GET /v1/harness/summary?window_secs=——成功率/失败率、平均迭代次数/持续时间、总 token 和 work item 队列深度;在页面上以卡片条形式显示。 GET /metrics上的指标:harness_runs_<status>_total、harness_run_duration_ms、harness_run_iterations、harness_run_tokens_total、harness_work_items_<status>(队列深度)。
12.12 Work items
Work item——测试框架的规范化工作单元(工单、支付、事件)。按 (source, external_id) 去重;生命周期:
new → claimed → running → done | failed | escalated。
来源(Jira/GitHub/邮件/webhook)发送 ingest——对同一 (source, external_id) 的重复 ingest 会更新内容,但不会重置状态:
POST /v1/harness/work-items
{ "source": "jira", "external_id": "PROJ-123",
"title": "Fix login", "body": "...", "priority": 2, "metadata": { } }
API(模块 harness,表 harness_work_items):
| 方法 | 用途 |
|---|---|
GET /v1/harness/work-items?status=&source= |
列表(过滤器,最新/优先在上) |
GET /v1/harness/work-items/:id |
单个 item |
POST .../:id/claim |
领取工作(lease;第二个 claim 失败) |
POST .../:id/status |
操作员手动更改状态 |
POST .../:id/run {workflow, input?} |
claim + 启动 harness 工作流;运行链接到 item |
关联运行完成后,item 状态会自动更新:success → done、cancelled → new(返回队列),否则 failed。
12.13 测试框架注册表
测试框架集合由注册表定义——config/harnesses/<id>.yaml 文件。测试框架是与领域无关的内部循环(loop/verify),不一定针对代码;workspace 是可选的。注册表将人类描述与执行工作流和工作来源关联起来。
id: jira-bugfix
title: Bugfix из тикета
description: |
Забирает дефект, воспроизводит, чинит в изолированном workspace,
прогоняет тесты и отдаёт на ревью.
workflow: jira-bugfix # workflow с блоком harness:
enabled: true
source:
kind: jira # какой источник кормит (* = любой)
auto_run: true # новый work item запускает харнесс сам
filter: { project: PROJ } # опц. равенство по metadata work item
defaults: { priority: 1 } # опц. значения по умолчанию
GET /v1/harness/harnesses——定义列表(在/harness上可见)。- 路由:在 ingest work item 时,选择第一个已启用且其
source.kind(和filter)匹配的测试框架;当auto_run: true时运行自行启动,否则通过POST /v1/harness/work-items/:id/run启动。 - 启动时从
config/harnesses/加载(与config/workflows/相邻的目录)。 - 面向工程师的 Operator 工具(代理
admin,通过tool_call):harness_list、harness_toggle、harness_summary、harness_runs、harness_work_items、harness_ingest、harness_run_item——在config/agents/toolbox/tools/中。